Elixir变量真的是不可变的吗?

Odh*_*che 68 immutability elixir

在Dave Thomas的书"编程Elixir"中,他指出"Elixir强制执行不可变数据"并继续说:

在Elixir中,一旦变量引用了诸如[1,2,3]之类的列表,您就会知道它将始终引用那些相同的值(直到您重新绑定变量).

这听起来像"除非你改变它,否则它不会改变"所以我对可变性和重新绑定之间的区别感到困惑.突出差异的一个例子非常有用.

Mir*_*mek 91

不要将Elixir中的"变量"视为命令式语言中的变量,即"值的空间".而是将它们视为"价值标签".

当你看到变量("标签")如何在Erlang中工作时,也许你会更好地理解它.每当你将一个"标签"绑定到一个值时,它就会永远与它绑定(范围规则当然适用于此).

在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)
Run Code Online (Sandbox Code Playgroud)

相反,你必须写这个:

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"
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这非常不方便,主要用于代码重构.如果要在第一行之后插入新行,则必须对所有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"
Run Code Online (Sandbox Code Playgroud)

简介:在命令式语言中,变量就像命名行李箱:你有一个名为"v"的行李箱.起初你把三明治放进去.比你放一个苹果(三明治丢失,也许被垃圾收集器吃掉).在Erlang和Elixir中,变量不是放置内容的地方.它只是一个值的名称/标签.在Elixir中,您可以更改标签的含义.在Erlang你不能.这就是为什么在Erlang或Elixir中"为变量分配内存"没有意义的原因,因为变量不占用空间.价值观.现在也许您可以清楚地看到差异.

如果你想深入挖掘:

1)看看Prolog中"未绑定"和"绑定"变量的工作原理.这就是这个可能有点奇怪的Erlang概念的源头"变量不变".

2)请注意,Erlang中的"="实际上不是赋值运算符,它只是一个匹配运算符!将未绑定变量与值匹配时,将变量绑定到该值.匹配绑定变量就像匹配它绑定的值一样.所以这会产生匹配错误:

v = 1,
v = 2,   % in fact this is matching: 1 = 2
Run Code Online (Sandbox Code Playgroud)

3)在Elixir中并非如此.所以在Elixir中必须有一个强制匹配的特殊语法:

v = 1
v = 2   # rebinding variable to 2
^v = 3  # matching: 2 = 3 -> error
Run Code Online (Sandbox Code Playgroud)

  • `x = x + 1`伤害了我的数学大脑.Erlang阻止了头痛,Elixir没有.:( (4认同)

Paw*_*rok 57

不可变性意味着数据结构不会改变.例如,该函数HashSet.new返回一个空集,只要您保持对该集的引用,它就永远不会变为非空.但是你在Elixir中可以做的是抛弃对某事物的变量引用并将其重新绑定到新的引用.例如:

s = HashSet.new
s = HashSet.put(s, :element)
s # => #HashSet<[:element]>
Run Code Online (Sandbox Code Playgroud)

什么不可能发生的事情是没有你明确地重新绑定它是参考变化下的值:

s = HashSet.new
ImpossibleModule.impossible_function(s)
s # => #HashSet<[:element]> will never be returned, instead you always get #HashSet<[]>
Run Code Online (Sandbox Code Playgroud)

与Ruby相比,您可以在其中执行以下操作:

s = Set.new
s.add(:element)
s # => #<Set: {:element}>
Run Code Online (Sandbox Code Playgroud)

  • 如果你想要不可变变量,你需要使用 Erlang 或在 Elixir 变量前加上“^”。重新绑定只是 Elixir 中的一个奇特术语,用于隐藏变量确实是可变的。请记住,我喜欢 Elixir,但我真的不喜欢社区试图将变量的可变性隐藏在花哨的术语和解释背后。 (2认同)
  • @sri我不关心实现细节,又名核心如何工作,我关心接口,又名如何使用它,因此如果我可以使用同一个变量两次并获得不同的值,那么对我来说不是一成不变的,而是可变的。现在你可以提供你想要的所有技术解释,我知道其中很多,但这永远不会改变 Elixir API 使用变量允许可变变量,但 Erlang API 使用变量只允许不可变变量。 (2认同)
  • @Exadra37 elixir 变量是不可变的,因为重新绑定变量只是重用变量名:它引用不同的内存位置,并且**重新绑定之前的任何代码仍将引用旧值,即使之后运行** 。可变性作为一个概念是指内存中的值,而不是引用哪个值。您可以将 Elixir 的重新绑定视为一种编译时技巧,(在重新绑定时和之后)将重命名变量,以便 VM 不会尝试为已分配的变量分配值(这实际上是 Elixir 编译器的行为方式) 。 (2认同)

Sub*_*dra 34

Erlang和显然是建立在它之上的Elixir,拥抱不变性. 它们只是不允许某个内存位置的值发生变化.从不直到变量被垃圾收集或超出范围.

变量不是不可改变的东西.他们指出的数据是不可改变的.这就是更改变量的原因称为重新绑定.

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

x = 1然后x = 2不会更改存储在1为2的计算机内存中的数据.它将2放在一个新的位置并指向x它.

x 只能由一个进程一次访问,因此这对并发性没有影响,并且即使有些东西是不可变的,并发性也是最关心的主要场所.

重新绑定根本不会改变对象的状态,该值仍然在同一个内存位置,但它的标签(变量)现在指向另一个内存位置,因此保留了不变性.重新绑定在Erlang中不可用,但是当它在Elixir中时,由于其实现,这不会制动Erlang VM施加的任何约束.JosèValim 在这个要点中很好地解释了这种选择背后的原因.

假设你有一个清单

l = [1, 2, 3]
Run Code Online (Sandbox Code Playgroud)

并且你有另一个进程正在采取列表,然后反复对它们执行"东西",并在此过程中更改它们将是不好的.您可以发送该列表

send(worker, {:dostuff, l})
Run Code Online (Sandbox Code Playgroud)

现在,您的下一部分代码可能希望使用更多值来更新l,以用于与其他进程正在执行的操作无关的进一步工作.

l = l ++ [4, 5, 6]
Run Code Online (Sandbox Code Playgroud)

哦不,现在第一个进程会有未定义的行为,因为你更改了列表吗?错误.

原始清单保持不变.你真正做的是根据旧列表创建一个新列表并将l重新绑定到新列表.

单独的进程永远不能访问l.最初指向的数据l保持不变,另一个进程(可能,除非它忽略它)有自己单独引用的原始列表.

重要的是,您不能跨进程共享数据,然后在另一个进程正在查看它时更改它.在Java这样的语言中你有一些可变类型(所有基本类型加上引用本身),就可以共享一个包含int的结构/对象,并在另一个线程读取它时从一个线程更改int.

实际上,当它被另一个线程读取时,可以在java中部分更改一个大整数类型.或者至少,它曾经是,不确定他们是否通过64位转换来限制事物的这一方面.无论如何,重点是,您可以通过在同时查看的位置更改数据来从其他进程/线程下拉出地毯.

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

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

在Erlang中,如果a = 3那么这就是该变量存在的持续时间的值,直到它退出范围并被垃圾收集.

这有时是有用的(在赋值或模式匹配后没有任何变化,因此很容易推断函数正在做什么)但如果你在执行函数的过程中对变量或集合做了多个事情,那也有点麻烦.

代码通常如下所示:

A=input, 
A1=do_something(A), 
A2=do_something_else(A1), 
A3=more_of_the_same(A2)
Run Code Online (Sandbox Code Playgroud)

这有点笨拙,使重构比实际需要更困难.Elixir在幕后执行此操作,但是通过编译器执行的宏和代码转换将其隐藏在程序员之外.

这里讨论很棒

不变性功能于灵药

  • +1非常明确的答案.它很好地解释了哪种是不可变性及其原因的编译器技术.这个答案连同Prymek的答案终于让我对这件事有了很好的理解.它们都应该是Elixir官方文档的一部分. (4认同)
  • 这个答案和其他答案一起最大程度地解释了这个主题 (2认同)

小智 5

从某种意义上说,变量确实是不可变的,每个新的重新绑定(赋值)仅对之后的访问可见。所有先前的访问仍然引用调用时的旧值。

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
Run Code Online (Sandbox Code Playgroud)