F# 中的阴影与设置值

Shadowing vs. Setting value in F#

有人告诉我,默认情况下,数据在 F# 中是不可变的。

当我们为某个变量重新赋值时,真正发生的是它重新绑定变量的值,但设置新值是另一回事。

重新绑定称为阴影,如果我们不明确说明,则不可能设置新值,变量的值是可变的。

谁能更详细地向我解释一下这个概念?

阴影(重新绑定)有什么区别:

let var = "new_value"

并设置一个新值,如:

var <- "new_value"

这一刻,在重新绑定期间我们创建了另一个对象并将该对象的地址分配给变量,而在第二个示例中我们更改了值本身?我从 heap/stack 概念中得到了它。但我可能是错的。

谢谢。

阴影是指当您创建一个 new 绑定时使用与先前绑定相同的名称。此 "shadows" 原始名称,它隐藏了它但不会更改或替换它。在 FSI 中试试看:

let foo = 42

let printFoo () = 
    printfn "%i" foo 

printFoo() ;;

这将打印:

42

val foo : int = 42
val printFoo : unit -> unit
val it : unit = ()

然后添加:

// ... more code
let foo = 24

printfn "%i" foo // prints 24
printFoo ();;

这将打印:

24
42

val foo : int = 24
val it : unit = ()

请注意,当您调用 printFoo() 时它仍然打印 42 - 该函数看到原始(无阴影)绑定,但新打印显示新值。

使用 <- 改变值需要可变绑定:

let mutable bar = 42

let printBar () = 
    printfn "%i" bar

printBar ();;

这与上面一样,打印 42。请注意,您在这里使用 mutable 关键字覆盖了默认的不可变行为。

然后您更改可变绑定中的值:

bar <- 24
printfn "%i" bar
printBar ();;

这将打印 24 两次,因为与阴影版本不同,突变改变了原始绑定。如果您在原始绑定中将 mutable 关闭,则在使用 <-.

时会出现错误

每当我想知道到底发生了什么时,我都会使用像 ILSpy

这样的工具

例如:

let f () =
  let x = Dictionary<int, string> ()
  let mutable x = ResizeArray<int> 16
  x <- ResizeArray<int> 16

用ILSpy反编译成C#代码变成:

public static void f()
{
  Dictionary<int, string> x = new Dictionary<int, string>();
  List<int> x2 = new List<int>(16);
  x2 = new List<int>(16);
}

到这里就更清楚shadowing和setting的区别了。

阴影 x 创建一个名为 x2 的新变量。

设置为正常赋值。

为了补充 Reed Copsey 的出色答案,如果您正在编写一个循环来更改某种累加器的值,则应将原始值设置为 mutable。例如,您可以这样做。

let mutable acc = 0 // declaration
for i in 1..100 do
    acc <- acc + i // assignment

这或多或少等同于 C# 代码:

var acc = 0;
for (int i = 1; i <= 100; i++)
{
    acc = acc + i; // assignment
    // Another way to write this:
    // acc += i;
}

但是,在阴影中,如 F# 片段中所示:

let acc = 0 // declaration
for i in 1..100 do 
    let acc = acc + i // another declaration only within the loop

你实际上没有做任何有用的事情!!第二个声明仅在 for 循环内有效,并且不会更改原始 acc.

的值

粗略的 C# 等价物是这样的:

var acc = 0; // declaration
for (int i = 1; i <= 100; i++)
{
    var acc2 = acc + i; // another declaration only within the loop
}

请注意,对内部变量使用 acc(而不是 acc2)不会在 C# 中编译,因为它在此上下文中没有阴影。

阴影的用途在于,它可以防止在您不想要的代码块中使用原始变体。因此,需要担心的变量少了一个。