可变值和不可变值重定义之间有什么区别?

use*_*677 4 f# functional-programming immutability mutability

我已经读过F#中的值是不可变的.但是,我也遇到了重新定义值定义的概念,它影响了以前的定义.这与可变值有什么不同?我这不仅仅是作为理论构造,而且还有关于何时使用可变值以及何时重新定义表达式的建议; 或者如果有人能够指出后者不是惯用的f#.

重新定义的基本示例:

let a = 1;;
a;; //1
let a = 2;;
a;; //2
Run Code Online (Sandbox Code Playgroud)

更新1:

添加到下面的答案中,顶级Fsharp交互式中的重新定义仅允许在不同的终端中.以下将在fsi中引发错误:

let a = 1
let a = 2;;

Error: Duplicate definition of value 'a'
Run Code Online (Sandbox Code Playgroud)

另一方面,let绑定允许重新定义.

更新2:实际差异,闭包不能使用可变变量:

let f =
   let mutable a = 1
   let g () = a //error
   0  
f;;
Run Code Online (Sandbox Code Playgroud)

更新3:

虽然我可以使用refs模拟副作用,例如:

let f =
   let  a = ref 1
   let g = a
   a:=2
   let x = !g  + !a
   printfn "x: %i" x //4

f;;
Run Code Online (Sandbox Code Playgroud)

我不太清楚重新定义和使用mutable关键字之间的实际区别,除了使用闭包的区别,例如:

let f  =
   let a = 1
   let g  = a
   let a = 2
   let x = g + a
   printfn "x: %i" x //3

f;;
Run Code Online (Sandbox Code Playgroud)

VS

let f =
   let mutable a = 1
   let g = a
   a <-2
   let x = g  + a
   printfn "x: %i" x //3
 f;;
Run Code Online (Sandbox Code Playgroud)

另一种想法:我不确定如何使用线程,但是(a)另一个线程可以在let绑定中改变可变变量的值,以及(b)另一个线程可以重新绑定/重新定义一个值内的值.让绑定.我肯定在这里遗漏了一些东西.

更新4:最后一种情况的不同之处在于,突变仍然会发生在嵌套范围内,而嵌套范围中的重新定义/重新绑定将从外部范围"影响"定义.

let f =
   let mutable a = 1
   let g = a
   if true then
      a <-2   
   let x = g  + a
   printfn "x: %i" x //3

f;;
Run Code Online (Sandbox Code Playgroud)

VS

let f =
   let a = 1
   let g = a
   if true then
      let a = 2  
      printfn "a: %i" a   
   let x = g  + a
   printfn "x: %i" x //2
f;;
Run Code Online (Sandbox Code Playgroud)

Ben*_*Ben 5

我对F#并不熟悉,但我可以回答"理论"部分.

使对象变异是(或至少有可能)全局可见的副作用.任何其他引用同一对象的代码都将观察到更改.现在可以更改在程序中任何位置依赖于对象值的任何属性.例如,如果以影响其排序位置的方式改变该列表中引用的对象,则列表已排序的事实可能会呈现为false.这可能是一个非常不明显和非本地的效果 - 处理排序列表的代码和执行变异的代码可能位于完全独立的库中(既不直接依赖另一个),只通过长链调用连接(其中一些可能是其他代码设置的闭包).如果您使用的突变相当广泛,则有可能甚至不是两个位置之间的直接调用链环节,这样一个事实可变对象清盘传递给那个不同诱变代码可能是依赖于进行操作的特定序列到目前为止,该计划已经完成.

另一方面,将局部变量从一个不可变值重新绑定到另一个不可变值可能仍然在技术上被视为"副作用"(取决于语言的确切语义),但它本身就是本地化的.因为它只对名称产生影响,而不是对前一个或后一个值产生影响,所以对象来自何处或者在此之后它们将去往何处并不重要.它改变访问该名称的其他代码位的含义; 您必须仔细检查受此影响​​的代码的位置仅限于名称范围.这是一种非常容易保持方法/函数/内部的副作用,因此当从外部视角观察时,该函数仍然是无副作用的(纯粹的;引用透明的) - 实际上没有捕获的闭包名字而不是价值我相信这种局部重新绑定不可能成为外部可见的副作用.