6

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 {}

V.B.
  • 6,236
  • 1
  • 33
  • 56
  • you don't really mutate anything - think about it like this: you change the name-binding of `a` (in your local scope) to a new value - and there are even things where I like it (for example something like this: `let f x = let x = if x < 0 then 0 else x ...`) – Random Dev Aug 17 '15 at 13:11
  • if you want to have *scoped* values you can always use `let a = 3 in ...` blocks – Random Dev Aug 17 '15 at 13:11
  • but why we are not forced to use `..in..`, like in C# when we are forced to use `{ }` for scopes? – V.B. Aug 17 '15 at 13:13
  • 1
    Let definitions are sequential, top-down order. `let a = 1; let a = 2` is called "outscoping" and it doesn't change the original value. It just means that the name of the value is no longer accessible from current scope. – Lukasz Szozda Aug 17 '15 at 13:36

3 Answers3

12

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).

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • Thanks, @Tomas! Is this an intended language feature that was thought to be useful? When this is more useful than a mutable variable - could you demonstrate a case when shadowing is better than mutation? – V.B. Aug 17 '15 at 13:27
  • From your other comment on the subject: http://stackoverflow.com/a/2478445/801189 I could see when shadowing is useful - if we have an immutable variable, e.g. an argument in a function, and we want to assign a new value to it, to avoid accidentally reusing the input value. This is done effectively to mutate an immutable value that was defined somewhere outside our code where we cannot add `mutable` from the beginning. – V.B. Aug 17 '15 at 13:43
  • 4
    One example where this is useful is parameter sanitizing. Your function takes a parameter that may have some undesired quality (like non-trimmed string or non-positive number or whatever), and the very first thing you do in the body is redefine that name to point to a sanitized value: `let f x = let x = sanitize x; calculateSomething x`. – Fyodor Soikin Aug 17 '15 at 13:55
  • @V.B. you can use outscoping to redefine (not overload) operators like `let (+) p q = p + q + 1`, but don't do this because it is misleading. – Lukasz Szozda Aug 17 '15 at 14:01
  • @V.B. Fyodor Soikin explains a good case for shadowing. You can see a variant of this approach in my answer here on the page. – Mark Seemann Aug 18 '15 at 10:37
7

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#.

Grundoon
  • 2,734
  • 1
  • 18
  • 21
5

Tomas Petricek already explains that this isn't mutation, but shadowing. One follow-up question is: what is it good for?

It isn't a feature I use every day, but sometimes I find it useful, particularly when doing property-based testing. Here's an example I recently did as part of doing the Tennis Kata with FsCheck:

[<Property>]
let ``Given player has thirty points, when player wins, then the new score is correct``
    (points : PointsData)
    (player : Player) =

    let points = points |> pointTo player Thirty

    let actual = points |> scorePoints player

    let expected =
        Forty {
            Player = player
            OtherPlayerPoint = (points |> pointFor (other player)) }
    expected =? actual

Here, I'm shadowing points with a new value.

The reason is that I want to explicitly test the case where a player already has Thirty points and wins again, no matter how many points the other player has.

PointsData is defined like this:

type Point = Love | Fifteen | Thirty
type PointsData = { PlayerOnePoint : Point; PlayerTwoPoint : Point }

but FsCheck is going to give me all sorts of values of PointsData, not only values where one of the players have Thirty.

This means that the points arriving as the function argument don't really represent the test case I'm interested in. To prevent accidental usage, I shadow the value in the test, while still using the input as a seed upon which I can build the actual test case value.

Shadowing can often be useful in cases like that.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736