Elixirvariables真的是不可变的吗?

在戴夫·托马斯(Dave Thomas)的“编程药剂”(Programming Elixir)一书中,他指出:“药剂执行不变数据”,并继续说:

在Elixir中,一旦一个variables引用了一个如[1,2,3]的列表,就知道它总是引用那些相同的值(直到重新绑定variables)。

这听起来像“它不会改变,除非你改变它”,所以我很困惑,可变性和重新绑定之间的区别是什么。 突出显示差异的例子将会非常有帮助。

不变性意味着数据结构不会改变。 例如, 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”中的“variables”看作命令式语言中的variables(“价值空间”)。 而是把它们看作“价值标签”。

当你看看Erlang中的variables(“标签”)是如何工作的时候,你可能会更好地理解它。 每当你将一个“标签”绑定到一个值时,它就会永远保持绑定(当然这里适用范围规则)。

在Erlang中,你不能写这个:

 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中,您可以重新绑定variables(改变“标签”的含义),主要是为了您的方便:

 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" 

简介:在命令式语言中,variables就像命名行李箱一样:你有一个名为“v”的行李箱。 起初,你把三明治放在里面。 比你把苹果放在里面(三明治丢失了,也许垃圾被收集起来)。 在Erlang和Elixir中,variables不是放置东西的地方 ,它只是一个值的名称/标签 。 在Elixir中,您可以更改标签的含义。 在二郎你不能。 这就是为什么在Erlang或Elixir中“为variables分配内存”没有意义,因为variables不占用空间。 价值观。 现在你也许看得清楚了。

如果你想深入挖掘:

1)看看“无约束”和“约束”variables在Prolog中是如何工作的。 这也许是Erlang的一些奇怪的“variables不变”的概念的来源。

2)注意Erlang中的“=”实际上不是一个赋值操作符,它只是一个匹配操作符! 将未绑定variables与值匹配时,将variables绑定到该值。 匹配绑定variables就像匹配绑定的值。 所以这会产生匹配错误:

 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 

Erlang和build立在它之上的显而易见的Elixir包含了不变性。 他们根本不允许某个内存位置的值改变。 从不直到variables被垃圾收集或超出范围。

variables不是不变的东西。 他们指出的数据是不可改变的。 这就是为什么改变一个variables被称为重新绑定。

你指的是别的东西,而不是改变它指向的东西。

x = 1然后是x = 2不会改变存储在计算机内存中的数据,其中1代表2。它将2放置在新位置并将x指向它。

x只能由一个进程访问,所以这对并发性没有影响,并发性是主要的地方,甚至在乎某些东西是不可变的。

重新绑定根本不改变对象的状态,值仍然在同一个内存位置,但是它的标签(variables)现在指向另一个内存位置,所以不变性被保留。 在Erlang中,Rebinding是不可用的,但是在Elixir中,这并没有制约Erlang虚拟机的任何约束,这要归功于它的实现。 这一select背后的原因在JosèValim 的主旨中得到很好的解释。

假设你有一个列表

 l = [1, 2, 3] 

而你有另一个进程正在清单,然后反复对他们进行“东西”,在这个过程中改变他们是不好的。 你可能会像这样发送这个列表

 send(worker, {:dostuff, l}) 

现在,你的下一个代码可能需要用更多的值更新l,以便与其他进程正在做的工作无关。

 l = l ++ [4, 5, 6] 

哦,不,现在第一个进程将会有不确定的行为,因为你改变了列表的权利? 错误。

原来的名单保持不变。 你真的做了一个新的名单,基于旧名单,并重新列入新的名单。

单独的进程永远不能访问l。 最初指出的数据是不变的,另一个过程(大概是,除非忽略它)有自己对原始列表的单独引用。

重要的是你不能跨进程共享数据,然后改变它,而另一个进程正在查看它。 在像Java这样的语言中,你有一些可变types(所有的原始types加上引用本身)就可以共享一个包含int的结构/对象,并在另一个线程中修改int。

实际上,在另一个线程读取的时候,可以部分地改变java中的一个大整数types。 或者至less,它曾经是,不知道是否他们用64位转换钳制这方面的东西。 无论如何,重要的是,您可以通过在同时查看的地方更改数据,从其他进程/线程下拉出地毯。

在Erlang和推广Elixir中这是不可能的。 这就是不变性在这里意味着什么。

为了更具体一点,在Erlang(VM Elixir的原始语言运行)中,一切都是单一赋值的不可变variables,Elixir隐藏了一个Erlang程序员开发的模式来解决这个问题。

在Erlang中,如果a = 3,那么在variables存在期间这个值就是它的值,直到它退出范围并被垃圾收集。

这在有些时候很有用(赋值或模式匹配后没有任何变化,所以很容易推理一个函数在做什么),但是如果在执行一个函数的过程中对variables或集合做了多个事情,也有点麻烦。

代码通常是这样的:

 A=input, A1=do_something(A), A2=do_something_else(A1), A3=more_of_the_same(A2) 

这有点笨重,使得重构比需要的更困难。 Elixir在后台执行此操作,但通过编译器执行的macros和代码转换将其从程序员中隐藏起来。

在这里大讨论

不变性function于灵药

variables实际上是不可改变的,每一个新的重新绑定(赋值)只有在访问之后才能看到。 所有以前的访问,在他们的通话时仍然是指旧的值。

 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