【问题标题】:Shadowing vs. Setting value in F#阴影与 F# 中的设置值
【发布时间】:2017-01-31 08:27:00
【问题描述】:

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

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

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

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

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

let var = "new_value"

并设置一个新值,如:

var <- "new_value"

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

谢谢。

【问题讨论】:

    标签: f# immutability shadowing


    【解决方案1】:

    Shadowing 是指您创建一个使用与先前绑定相同名称的 绑定。这会“隐藏”原始名称,将其隐藏但不会更改或替换它。在 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 - 该函数会看到原始(无阴影)绑定,但新打印会显示新值。

    使用&lt;- 改变一个值需要一个可变绑定:

    let mutable bar = 42
    
    let printBar () = 
        printfn "%i" bar
    
    printBar ();;
    

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

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

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

    这将打印 24 两次,因为与阴影版本不同,突变会更改原始绑定。如果您在原始绑定中将mutable 关闭,则在使用&lt;- 时会出现错误。

    【讨论】:

      【解决方案2】:

      为了补充 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# sn-p 中一样:

      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# 中不会编译,因为它在此上下文中没有阴影。

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

      【讨论】:

        【解决方案3】:

        每当我想知道到底发生了什么时,我都会使用 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 的新变量。

        设置是正常的赋值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-09-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多