这听起来像是“除非你改变它,否则它永远不会改变”,因此我对可变性和重新绑定之间的区别感到困惑。一个突显差异的例子将非常有帮助。在Elixir中,一旦一个变量引用了一个列表,比如[1,2,3],你就知道它将始终引用这些相同的值(直到你重新绑定这个变量)。
这听起来像是“除非你改变它,否则它永远不会改变”,因此我对可变性和重新绑定之间的区别感到困惑。一个突显差异的例子将非常有帮助。在Elixir中,一旦一个变量引用了一个列表,比如[1,2,3],你就知道它将始终引用这些相同的值(直到你重新绑定这个变量)。
v = 1, % value "1" is now "labelled" "v"
% wherever you write "1", you can write "v" and vice versa
% the "label" and its value are interchangeable
v = v+1, % you can not change the label (rebind it)
v = v*10, % you can not change the label (rebind it)
相反,你必须写成这样:
v1 = 1, % value "1" is now labelled "v1"
v2 = v1+1, % value "2" is now labelled "v2"
v3 = v2*10, % value "20" is now labelled "v3"
正如您看到的,这非常不方便,特别是在代码重构时。如果您想在第一行后插入一个新行,您将不得不重新编号所有v*或编写类似"v1a = ..."的东西。
因此,在Elixir中,您可以重新绑定变量(更改“标签”的含义),主要是为了方便起见:
v = 1 # value "1" is now labelled "v"
v = v+1 # label "v" is changed: now "2" is labelled "v"
v = v*10 # value "20" is now labelled "v"
简述: 在命令式语言中,变量就像是一个个被命名的手提箱:你有一个叫做 "v" 的手提箱。一开始你把三明治放进去。然后你把一个苹果放进去(三明治丢失了,也许被垃圾回收器吃掉了)。在 Erlang 和 Elixir 中,变量不是 放置 东西的地方。它只是一个值的 名称/标签 。在 Elixir 中,您可以更改标签的含义。在 Erlang 中,您不能。这就是为什么在Erlang或Elixir中,“为变量分配内存”没有意义的原因,因为变量不占用空间。值才占用空间。现在您可能已经清楚地看到了差异。
如果你想更深入的了解:
1)看看Prolog中“未绑定”和“绑定”变量是如何工作的。这是这个可能有些奇怪的Erlang概念“不变的变量”的源头。
2)请注意,Erlang中的“=”实际上不是赋值运算符,它只是匹配运算符!当将一个未绑定的变量与一个值匹配时,您将该变量绑定到该值。匹配一个绑定变量就像匹配它绑定的值一样。所以这将产生一个 匹配 错误:
v = 1,
v = 2, % in fact this is matching: 1 = 2
3) 在 Elixir 中不是这样的。因此,在 Elixir 中必须有一种特殊的语法来强制匹配:
v = 1
v = 2 # rebinding variable to 2
^v = 3 # matching: 2 = 3 -> error
x = x +1
让我的数学大脑感到疼痛。Erlang可以止痛,但 Elixir 不行。 :( - Kevin Monk不变性意味着数据结构不会改变。例如,函数HashSet.new
返回一个空集合,只要保持对该集合的引用,它就永远不会变为非空。在Elixir中,你可以放弃对某个变量的引用并将其重新绑定到一个新的引用。例如:
s = HashSet.new
s = HashSet.put(s, :element)
s # => #HashSet<[:element]>
在没有显式地重新绑定它的情况下,引用下的值不会发生更改:
s = HashSet.new
ImpossibleModule.impossible_function(s)
s # => #HashSet<[:element]> will never be returned, instead you always get #HashSet<[]>
相比之下,Ruby可以执行以下类似操作:
s = Set.new
s.add(:element)
s # => #<Set: {:element}>
^
视为仅用其值替换变量。此运算符在模式匹配等方面发挥了大部分作用。至于不可变性,对于所有目的而言,Elixir确实是不可变的。如果有意更改变量的值,则指向该变量的位置存储的值本身不能被突变为另一个值。相反,变量会重新绑定到可以存储新值的新位置。因此术语“重新绑定”。 - Sri Kadimisettyx=1
后跟x=2
并不会将计算机内存中存储1的数据更改为2。它将2放置在新位置并将x
指向它。由于x
一次只能由一个进程访问,因此这对并发没有任何影响,而并发恰好是需要关注不可变性的主要领域。l = [1, 2, 3]
如果您有另一个进程,需要反复对列表执行"操作"并在此过程中更改它们,则更改它们可能会产生问题。您可以像这样发送该列表:
send(worker, {:dostuff, l})
l = l ++ [4, 5, 6]
哦,不好了,因为你改变了列表,第一个进程现在会出现未定义的行为,对吗?错。
原始列表保持不变,你真正做的是基于旧列表创建一个新列表,并将 l 重新绑定到该新列表。
单独的进程从未访问过 l。l 最初指向的数据保持不变,另一进程(可能是这样,除非它忽略了它)具有其自己独立于原始列表的引用。
重要的是不能共享跨进程的数据并在另一个进程查看时更改它。在像 Java 这样的语言中,你有一些可变类型(所有基本类型加上引用本身),可以共享包含 int 的结构/对象,并在一个线程更改它时,在另一个线程读取它。
实际上,在 Java 中部分更改大整数类型,同时被另一个线程读取是可能的。或者至少,过去是这样,不确定他们在 64 位转换时是否限制了该方面的事情。总之,问题是,通过在两个同时查看的地方更改数据,你可以使其他进程/线程失去基础。
在 Erlang 和 Elixir 中是不可能的,这就是这里不可变的含义。
更具体地说,在Erlang中(Elixir运行的原始语言),一切都是单赋值不可变变量,而Elixir隐藏了Erlang程序员开发的一种模式以解决此问题。A=input,
A1=do_something(A),
A2=do_something_else(A1),
A3=more_of_the_same(A2)
x = 1; f = fn -> x end; x = 2; #=> 2
时,f
lambda甚至不通过x
访问x变量,而是通过一个次要引用来访问吗?这就是它如何能够仍然访问1,即使x被设置为2的原因吗?我知道这部分来自Elixir的立即编译,但是除此之外,是否有第二个引用用于在重新绑定发生后访问数据? - Tallboy变量在本质上是不可变的,每次重新绑定(赋值)只对其后访问可见。所有之前的访问仍然指向它们调用时的旧值。
foo = 1
call_1 = fn -> IO.puts(foo) end
foo = 2
call_2 = fn -> IO.puts(foo) end
foo = 3
foo = foo + 1
call_3 = fn -> IO.puts(foo) end
call_1.() #prints 1
call_2.() #prints 2
call_3.() #prints 4