在列表理解中重新分配变量
Reassigning a variable in list comprehension
我是 Elixir 和函数式编程的新手,具有 OO 背景。
我一直在尝试了解 Elixir 中变量重新分配在列表理解中的工作原理。我希望函数 test1() 和 test2() 打印 4,但 test2() 不会重新分配变量并打印 1.
defmodule SampleCode do
def test1 do
num = 1
num = num + 1
num = num + 2
IO.puts num # Prints 4
end
def test2 do
num = 1
for i <- (1..2) do
num = num + i
end
IO.puts num # Prints 1
end
end
- 为什么这些函数的行为不同?
- 这是 Elixir 中的变量作用域还是我遗漏的函数式编程的基本原则?
Is this a variable scoping thing in Elixir or a basic principle in functional programming that I'm missing?
其实两者都是
Elixir 仅允许在相同范围和所有构造中重新绑定,case
、cond
和 receive
除外,引入新范围。一些例子:
num = 1
try do
num = 2
after
num = 3
end
num #=> 1
乐趣:
num = 1
(fn -> num = 2 end).()
num #=> 1
现在是规则例外的一些示例:
num = 1
case true do
true -> num = 2
end
num #=> 2
num = 1
cond do
true -> num = 2
end
num #=> 2
尽管如此,上述情况还是有些令人沮丧,因为它比 return 明确的值更可取:
num = 1
case x do
true -> 2
false -> num
end
#=> will return 1 or 2
上面的示例明确说明了从大小写 return 编辑的值是什么。为什么这些在 Elixir 中得到支持,尽管不被推荐,这是一个很长的故事,它起源于 Erlang,并且由于 Elixir 中的一些(非常少的)命令式宏而继续存在,比如 if
和 unless
。随着我们转向 Elixir 2.0,它可能会发生变化。
执行所需操作的最佳方式是通过 Enum
中的函数。您的特定示例可以用 Enum.sum/1
完成,但任何其他复杂示例都可以用 Enum.reduce/3
实现(Enum 中的几乎所有函数都是根据 reduce 实现的,这可能在其他语言中被折叠)。
要了解发生了什么,请看一下 elixir 对您的代码做了什么以在语法上允许重新绑定,当语言*不支持它时:
BEFORE AFTER
defmodule SampleCode do | defmodule SampleCode do
def test1 do | def test1 do
num = 1 | num_1 = 1
num = num + 1 | num_2 = num_1 + 1
num = num + 2 | num_3 = num_2 + 2
IO.puts num # Prints 4 | IO.puts num_3
end | end
|
def test2 do | def test2
num = 1 | num_1 = 1
for i <- (1..2) do | for i <- (1..2) do
num = num + i | num_2 = num_1 + i
end | end
IO.puts num # Prints 1 | IO.puts num_1
end | end
end | end
*Elixir 的基础是 Erlang——Elixir 的编译生成 Erlang .beam 文件,由 Erlang 虚拟机执行。 Erlang 不允许变量重新绑定,所以 Elixir 在编译期间换掉你的变量名。
如果你想提高你的 FP 游戏水平,并在编写 Elixir 时给自己一个巨大的优势,我建议你强迫自己学习 Erlang 语法,并用一个月的时间严格编写 Erlang 解决方案来解决你的问题或很少再回到 Elixir。
如果您想知道您的代码实际发生了什么,这里是 Elixir 生成的实际 Erlang:
1: test1() ->
2: num@1 = 1,
3: num@2 = num@1 + 1,
4: num@3 = num@2 + 2,
5: 'Elixir.IO':puts(num@3).
6: test2() ->
7: num@1 = 1,
8: 'Elixir.Enum':reduce(#{'__struct__' => 'Elixir.Range',
9: first => 1, last => 2},
10: [], fun (i@1, _@1) -> num@2 = num@1 + i@1 end),
11: 'Elixir.IO':puts(num@1).
特别注意第 11 行 - 它引用的唯一变量是 num@1
,在 Erlang 中,第 8-10 行不能更改它 - 无论那里发生什么,值绑定到 num@1
仍然是 1
,因为这是第 7 行第一次绑定的方式。
我是 Elixir 和函数式编程的新手,具有 OO 背景。
我一直在尝试了解 Elixir 中变量重新分配在列表理解中的工作原理。我希望函数 test1() 和 test2() 打印 4,但 test2() 不会重新分配变量并打印 1.
defmodule SampleCode do
def test1 do
num = 1
num = num + 1
num = num + 2
IO.puts num # Prints 4
end
def test2 do
num = 1
for i <- (1..2) do
num = num + i
end
IO.puts num # Prints 1
end
end
- 为什么这些函数的行为不同?
- 这是 Elixir 中的变量作用域还是我遗漏的函数式编程的基本原则?
Is this a variable scoping thing in Elixir or a basic principle in functional programming that I'm missing?
其实两者都是
Elixir 仅允许在相同范围和所有构造中重新绑定,case
、cond
和 receive
除外,引入新范围。一些例子:
num = 1
try do
num = 2
after
num = 3
end
num #=> 1
乐趣:
num = 1
(fn -> num = 2 end).()
num #=> 1
现在是规则例外的一些示例:
num = 1
case true do
true -> num = 2
end
num #=> 2
num = 1
cond do
true -> num = 2
end
num #=> 2
尽管如此,上述情况还是有些令人沮丧,因为它比 return 明确的值更可取:
num = 1
case x do
true -> 2
false -> num
end
#=> will return 1 or 2
上面的示例明确说明了从大小写 return 编辑的值是什么。为什么这些在 Elixir 中得到支持,尽管不被推荐,这是一个很长的故事,它起源于 Erlang,并且由于 Elixir 中的一些(非常少的)命令式宏而继续存在,比如 if
和 unless
。随着我们转向 Elixir 2.0,它可能会发生变化。
执行所需操作的最佳方式是通过 Enum
中的函数。您的特定示例可以用 Enum.sum/1
完成,但任何其他复杂示例都可以用 Enum.reduce/3
实现(Enum 中的几乎所有函数都是根据 reduce 实现的,这可能在其他语言中被折叠)。
要了解发生了什么,请看一下 elixir 对您的代码做了什么以在语法上允许重新绑定,当语言*不支持它时:
BEFORE AFTER
defmodule SampleCode do | defmodule SampleCode do
def test1 do | def test1 do
num = 1 | num_1 = 1
num = num + 1 | num_2 = num_1 + 1
num = num + 2 | num_3 = num_2 + 2
IO.puts num # Prints 4 | IO.puts num_3
end | end
|
def test2 do | def test2
num = 1 | num_1 = 1
for i <- (1..2) do | for i <- (1..2) do
num = num + i | num_2 = num_1 + i
end | end
IO.puts num # Prints 1 | IO.puts num_1
end | end
end | end
*Elixir 的基础是 Erlang——Elixir 的编译生成 Erlang .beam 文件,由 Erlang 虚拟机执行。 Erlang 不允许变量重新绑定,所以 Elixir 在编译期间换掉你的变量名。
如果你想提高你的 FP 游戏水平,并在编写 Elixir 时给自己一个巨大的优势,我建议你强迫自己学习 Erlang 语法,并用一个月的时间严格编写 Erlang 解决方案来解决你的问题或很少再回到 Elixir。
如果您想知道您的代码实际发生了什么,这里是 Elixir 生成的实际 Erlang:
1: test1() ->
2: num@1 = 1,
3: num@2 = num@1 + 1,
4: num@3 = num@2 + 2,
5: 'Elixir.IO':puts(num@3).
6: test2() ->
7: num@1 = 1,
8: 'Elixir.Enum':reduce(#{'__struct__' => 'Elixir.Range',
9: first => 1, last => 2},
10: [], fun (i@1, _@1) -> num@2 = num@1 + i@1 end),
11: 'Elixir.IO':puts(num@1).
特别注意第 11 行 - 它引用的唯一变量是 num@1
,在 Erlang 中,第 8-10 行不能更改它 - 无论那里发生什么,值绑定到 num@1
仍然是 1
,因为这是第 7 行第一次绑定的方式。