Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

F# allows re-defining name although documentation says otherwise #9900

Closed
JohnyL opened this issue Aug 8, 2020 · 14 comments
Closed

F# allows re-defining name although documentation says otherwise #9900

JohnyL opened this issue Aug 8, 2020 · 14 comments

Comments

@JohnyL
Copy link

JohnyL commented Aug 8, 2020

Please provide a succinct description of the issue.

The documentation says:

/// The second line of code fails to compile because 'number' is immutable and bound.
/// Re-defining 'number' to be a different value is not allowed in F#.
let number = 2
// let number = 3

This is not true. The following compiles and executes perfectly:

[<EntryPoint>]
let main argv =
    let number = 2
    printfn "number: %d" number
    let number = 3
    printfn "number: %d" number
    0
// Output:
//    number: 2
//    number: 3

Related information

  • Operating system: Windows 10 x64 Pro 1909
  • .NET 5.0 Preview 7
  • Visual Studio 2019 Preview 16.8.0 Preview 1.0
@dsyme
Copy link
Contributor

dsyme commented Aug 8, 2020

This is by design - the restiction applies to definitions which result in an accessible, not for local definitions in expressions, e.g. this is disallowed:

module M =
    let number = 2
    printfn "number: %d" number
    let number = 3
    printfn "number: %d" number

but in your own example the let number bindings are locally scoped bindings in an expression.

Sometimes it's helpful to think in terms of the one-line expression form let x = expr in result:

[<EntryPoint>]
let main argv =
    let number = 2 in printfn "number: %d" number; let number = 3 in printfn "number: %d" number; 0

@dsyme dsyme closed this as completed Aug 8, 2020
@JohnyL
Copy link
Author

JohnyL commented Aug 8, 2020

@dsyme Please, read more carefully documentation. It doesn't say about whether they are local. There's a statement which says "fails to compile". I checked it - compiles fine.

@KevinRansom
Copy link
Member

@JohnyL --

I agree that the documentation doesn't make clear the distinction between a let binding on a module and local bindings, and expects the reader to figure it out. Perhaps you could suggest a way of clarifying the documentation, at the bottom of the page is a feedback button for the page:

For example perhaps the comment in the documentation could be amended:

module Immutability =

    /// Binding a value to a name via 'let' makes it immutable.
    /// When binding values on a module these names may not be reused.
    ///
    /// The second line of code fails to compile because 'number' is immutable and bound.
    /// Re-defining 'number' to be a different value is not allowed in F#.
    let number = 2
    // let number = 3

  

Perhaps you could provide feedback to the documentation team, and they can work on making it clearer. There is a link at the bottom of each page of documentation.

image

@JohnyL
Copy link
Author

JohnyL commented Aug 8, 2020

@KevinRansom I have already done that. 😉 Anyway, it's strange design. If F# relies on immutability of this binding, then I see no immutability here. It's like in JavaScript with var.

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Aug 8, 2020

then I see no immutability here.

@JohnyL The values are immutable, what you're doing is rebinding a variable identifier, not changing the value at that memory location. This shadows the original name and under the hood, F# will create a new name.

It's still immutable, you cannot change the value and at the same time keep the same memory location. You can use mutable for that, and the <- syntax.

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Aug 8, 2020

Here's an example

let x = 3
for i = 0 to 10 do
    let x = i
    ignore() 

printfn "%i" x // prints 3

Compare that with

let mutable x = 3
for i = 0 to 10 do
    x <- i
    ignore() 

printfn "%i" x // prints 10

Likewise, you compared it with var. But in C#, you declare once, and assign many (and you cannot use var on the new assignments, if you would, that would mean a new variable is declared, same in Javascript). In F#, you declare once and assign once: you create a mapping between the identifier and the value, and this cannot change. If you use a new declaration, be it the same name or not, it's just that: a new declaration of a new let binding.

@BentTranberg
Copy link

@JohnyL, you definitely point to a problem with that piece of documentation.

I understand that you are new to F#, and would like to suggest that you join one of the discussion forums of the F# community in order to discuss issues like this one. In particular, all of us commenting so far are just a few among thousands of F# programmers in the F# Slack forum, and we can help with pretty much any topic. We'd love to welcome you there.

@JohnyL
Copy link
Author

JohnyL commented Aug 8, 2020

@abelbraaksma Thanks for explanation, but your example is a bit different. You're redefining in different scope, while my example (more accurately, doc's example) is in the same scope. If I can redefine the variable in the same scope, then what's the point in immutability? What's the difference between this:

let GetData =
    50
let x = GetData
// ... some code ...
let x = 100 //Redefining
// ... some code ...
let z = x //Oops, z expects 50, but gets 100

and this:

let mutable x = 50
// ... some code ...
x <- 100
// ... some code ...
let z = x

?

@BentTranberg
Copy link

BentTranberg commented Aug 8, 2020

@JohnyL, what you are struggling to understand is called shadowing. That's the name of the concept. It is only allowed in a local scope.

This is explained in this SO issue. But I'd like to have a go at explaining it myself too.

The trick is simply that the name - and only the name - is reused for what is actually a totally new binding that is not related to the old binding. That means that in your example, you have two local bindings with the same name. The one defined last is said to be shadowing the one defined first.

Let me give you another example to clarify further.

let demonstrateShadowing () =
    let x = 7
    let x = float x + 3.5
    printfn "The x is %f" x

There are two bindings here - the first x, and the second x. You can also see that the second binding's expression can access the first, but after that the print statement would not be able to refer to the first x, because it has been shadowed by the second. In this example we also happen to have different types on the two bindings, which is no problem since they have nothing to do with each other, besides having the same name. The first x is an int, and the second x is a float.

Why shadowing? There are advantages. It blocks trying to access the first x by its name. It helps make intentions clearer. In F# we tend to think about how data flows, and in this case it's clearer that we're calculating something we'd like to call x step by step, reusing the name along the way.

I hope this clarified things.

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Aug 8, 2020

And to add to that excellent explanation, recall that a newline is light syntax for using in. If you would explicitly scope each and every let binding with in, the meaning becomes clearer and the shadowing goes away.

However, that also makes it harder to code.

Btw, the shadowing in one scope or in nested scopes is technically the same, each let introduces a new scope, be it on the same indentation level or not.

If, and only if, you're assigning a value to an existing binding using <-, are jou using mutable data.

@JohnyL
Copy link
Author

JohnyL commented Aug 8, 2020

@abelbraaksma @BentTranberg Thanks for explanation! Now the picture is clear!😉 OFF: I must use my work e-mail to enter F# Slack forums?

@BentTranberg
Copy link

I did not register with my work email, but with my private GMail address. Guess you can use whichever you want.

@abelbraaksma
Copy link
Contributor

abelbraaksma commented Aug 8, 2020

@JohnyL FYI, From another little research, I happened upon this little beauty that explains this concept very easily and succinctly:

image

Source: https://fsharpforfunandprofit.com/posts/let-use-do/

@JohnyL
Copy link
Author

JohnyL commented Aug 9, 2020

@abelbraaksma Thanks for clarifying! 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants