Search code examples
f#immutabilityshadowing

Shadowing vs. Setting value in F#


I was taught, that data, by default, is immutable in F#.

When we reassign value to some variable, what really happens is that it rebinds the value of variable, but setting a new value is different thing.

Rebinding is called Shadowing whilst setting new value is impossible if we don't say explicitly, that value of the variable is mutable.

Can anyone explain to me this concept in a bit more details?

What is the difference between shadowing (rebinding):

let var = "new_value"

and setting a new value, as:

var <- "new_value"

Is this a moment, that during rebinding we create another object and we assign that object's address to the variable, whereas in the second example we change the value itself? I brought that from heap/stack concept.. but I may be wrong.

Thanks.


Solution

  • Shadowing is when you create a new binding that uses the same name as a previous binding. This "shadows" the original name, which hides it but doesn't change or replace it. Try this in FSI to see:

    let foo = 42
    
    let printFoo () = 
        printfn "%i" foo 
    
    printFoo() ;;
    

    This will print:

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

    Then add:

    // ... more code
    let foo = 24
    
    printfn "%i" foo // prints 24
    printFoo ();;
    

    This will print:

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

    Note that it still prints 42 when you call printFoo() - the function sees the original (unshadowed) binding, but the new print shows the new value.

    Using <- to mutate a value requires a mutable binding:

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

    This, like above, prints 42. Note that you override the default immutable behavior here with the mutable keyword.

    You then change the value within the mutable binding:

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

    This will print 24 twice, since, unlike the shadowed version, the mutation changes the original binding. If you leave mutable off in the original binding, you'll get an error when using <-.