Why multiple bindings are possible inside a method in F #

A colleague reviewed my code and asked why I used the following template:

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 this is a bad model, and I intend to use let mutable a = ... But then he asked why there are such consistent bindings in the F # class at all, while this is not possible in the module or the .fsx file? In essence, we mutate a constant value!

I replied that in the module or in the .fsx file, the let binding becomes a static method, so directly binding with the same name conflicts in the same way as two properties of the class with the same name. But I do not know why this is possible inside the method!

In C #, I found scoping variables useful, especially in unit tests, when I want to compose several different values ​​for test cases by simply copying code fragments:

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

In F #, we could not only repeat the same binding bindings, but change immutable to mutable, and then mutate it later in 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 are we allowed to repeat binding bindings in F # methods? How to explain this to a person who is new to F # and asked: "What is an immutable variable and why am I mutating it?"

Personally, I’m interested in what design options and compromises were made to allow this? It’s convenient for me when various areas of visibility are easily detected, for example. when the variable is in the constructor, and then we redefine it in the body or define a new binding inside the for / while loops. But two consecutive bindings on the same level are somewhat contrary to intuition. It seems to me that I should mentally add in at the end of each line, as in the detailed syntax, in order to explain the scope, so those virtual in look like C # {}

+4
source share
3 answers

I think it’s important to explain that something different happens in F # than in C #. Variable shading does not replace a symbol - it is simply the definition of a new symbol that has 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 calculations using mutations in C #:

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

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

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

This works — but we don’t need the temporary states that we identified during the build process. So in F # you can use the shading variable to hide states you no longer need:

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

Now that means the same thing — and you can show it to people using Visual F # Power Tools that highlight all occurrences of a character — so you will see that the characters are different (although they have the same name).

+8
source

Code F #:

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

- This is just a compressed (so-called "light") version:

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

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

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

In the context of nested areas, C # does not allow names to be obscured, but F # and almost all other languages.

In fact, according to the font of all knowledge, C # is unusual in that it is one of the few languages ​​that explicitly prohibit shading in this situation.

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

+6
source

Thomas Petrichek already explains that this is not a mutation, but a shadow. One subsequent question: what is good for?

This is not a feature that I use every day, but sometimes I find it useful, especially when testing based on properties. Here is an example I recently made as part of 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 am shading points new value.

The reason is because I want to explicitly test the case when a player already has Thirty and wins again, regardless of how many points the other player has.

PointsData defined as follows:

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

but FsCheck will give me all sorts of PointsData values, not just the values ​​in which one of the players has Thirty .

This means that the points included as an argument to the function are not really a test case in which I am interested. To prevent accidental use, I obscure the value in the test, but still use the input as a seed on which I can build the actual value of the test case.

Shadow can often be useful in such cases.

+4
source

All Articles