我已经介绍过,默认情况下,数据在F#中是不可变的。当我们将值重新赋值给某个变量时,真正发生的是它重新绑定变量的值,但设置新值是不同的事情。 重新绑定被称为阴影,而如果我们明确表示变量的值是可变的,则设置新值是不可能的。
有人可以详细解释这个概念吗?
之间的阴影(重新绑定)之间的区别let var = "new_value"
并设置新值,如
var <- "new_value"
这是一个时刻,在重新绑定期间我们创建另一个对象并将该对象的地址分配给变量,而在第二个示例中我们更改值本身?我从堆/堆栈中了解了内存......我可能错了。
由于
答案 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
的新变量。
设置是正常分配。