Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why multiple let bindings are possible inside a method in F#

Tags:

.net

f#

A colleague reviewed my code and asked why I was using the following pattern:

type MyType() =
  member this.TestMethod() =
    let a = 1
    // do some work
    let a = 2 // another value of a from the work result
    ()

I told him that that was a bad pattern and my intent was to use let mutable a = .... But then he asked why at all it is possible in F# class to have such sequential bindings, while it is not possible in a module or an .fsx file? In effect, we are mutating an immutable value!

I answered that in a module or an .fsx file a let binding becomes a static method, therefore it is straightforward that the bindings with the same name will conflict in the same way as two class properties with the same name do. But I have no idea why this is possible inside a method!

In C#, I have found useful to scope variables, especially in unit tests when I want to make up some different values for test cases by just copy-pasting pieces of code:

{
    var a = 1;
    Assert.IsTrue(a < 2);
}

{
    var a = 42;
    Assert.AreEqual(42, a);
}

In F#, we could not only repeat the same let bindings, but change an immutable one to a mutable one and then mutate it later the usual way:

type MyType() =
  member this.TestMethod() =
    let a = 1
    // do some work
    let a = 2 // another value of a from the work result
    let mutable a = 3
    a <- 4
    ()

Why we are allowed to repeat let bindings in F# methods? How should I explain this to a person who is new to F# and asked "What is an immutable variable and why I am mutating it?"

Personally, I am interested in what design choices and trade offs were made to allow this? I am comfortable with the cases when the different scopes are easily detectable, e.g. when a variable is in constructor and then we redefine it in the body, or when we define a new binding inside a for/while loops. But two consecutive bindings at the same level are somewhat counter-intuitive. It feels like I should mentally add in to the end of each line as in the verbose syntax to explain the scopes, so that those virtual ins are similar to C#'s {}

like image 785
V.B. Avatar asked Aug 17 '15 13:08

V.B.


2 Answers

I think it is important to explain that there is a different thing going on in F# than in C#. Variable shadowing is not replacing a symbol - it is simply defining a new symbol that happens to have the same name as an existing symbol, which makes it impossible to access the old one.

When I explain this to people, I usually use an example like this - let's say we have a piece of code that does some calculation using mutation in C#:

var message = "Hello";
message = message + " world";
message = message + "!";

This is nice because we can gradually build the message. Now, how can we do this without mutation? The trick is to define new variable at each step:

let message1 = "Hello";
let message2 = message1 + " world";
let message3 = message2 + "!";

This works - but we do not really need the temporary states that we defined during the construction process. So, in F# you can use variable shadowing to hide the states you no longer care about:

let message = "Hello";
let message = message + " world";
let message = message + "!";

Now, this means exactly the same thing - and you can nicely show this to people using Visual F# Power Tools, which highlight all occurrences of a symbol - so you'll see that the symbols are different (even though they have the same name).

like image 178
Tomas Petricek Avatar answered Nov 15 '22 11:11

Tomas Petricek


The F# code:

let a = 1
let a = 2
let a = 3
a + 1

is just a condensed (aka "light") version of this:

let a = 1 in
    let a = 2 in
        let a = 3 in
            a + 1

The (sort of) C# equivalent would be something like this:

var a = 1;
{
    var a = 2;
    {
        var a = 3;
        return a + 1;
    }
}

In the context of having nested scopes, C# doesn't allow shadowing of names, but F# and almost all other languages do.

In fact, according to the font of all knowledge C# is unusual in being one of the few languages that explicitly disallow shadowing in this situation.

This might be because C# is a relatively new language. OTOH F# copies much of its design from OCaml, which in turn is based on older languages, so in some sense, the design of F# is "older" than C#.

like image 25
Grundoon Avatar answered Nov 15 '22 11:11

Grundoon