F# 中的阴影与设置值

问题描述 投票:0回答:3

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

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

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

有人可以更详细地向我解释这个概念吗?

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

let var = "new_value"

并设置一个新值,如:

var <- "new_value"

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

f# immutability shadowing
3个回答
17
投票

Shadowing 是指您创建一个使用与先前绑定相同名称的 new 绑定。这会“隐藏”原始名称,隐藏它但不会更改或替换它。在 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
,则在使用
<-
时会出现错误。


5
投票

补充 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;
}

但是,在 Shadowing 中,如以下 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# 中编译,因为它在此上下文中没有 Shadowing。

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


4
投票

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

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

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

设置为正常赋值。

© www.soinside.com 2019 - 2024. All rights reserved.