F#中的阴影与设置值

时间:2016-09-22 16:37:36

标签: f# immutability shadowing

我已经介绍过,默认情况下,数据在F#中是不可变的。当我们将值重新赋值给某个变量时,真正发生的是它重新绑定变量的值,但设置新值是不同的事情。 重新绑定被称为阴影,而如果我们明确表示变量的值是可变的,则设置新值是不可能的。

有人可以详细解释这个概念吗?

之间的阴影(重新绑定)之间的区别
let var = "new_value"

并设置新值,如

var <- "new_value"

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

由于

3 个答案:

答案 0 :(得分:13)

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

答案 1 :(得分: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。

阴影的使用是,它阻止在您不想要它的代码块中使用原始变体。因此,担心的变量会少一个。

答案 2 :(得分:2)

每当我想知道实际发生了什么时,我就会使用像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);
}

这里阴影和设置的区别更明显。

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

设置是正常分配。