Search code examples
f#booleanoperator-precedence

Why is the effect of newlines in F# different for certain expressions, and (arguably) counter-intuitive in some of them?


Disclaimer/edit: this is a fairly simple question, but I'm asking it since I'm (still) often confused by evaluation order in F#, esp. with respect to newlines vs. spaces. Trial and error always gets me where I want to, but I don't think you can really understand a language if you have to resort to trial and error.

If I write:

let res x =
    x * 1 = x
    |> ignore

all's fine, but if I write:

let res x =
    x * 1 = x && x * -1 = -x
    |> ignore

then the compiler complains (it says it expects bool -> bool, not bool -> unit). I would have expected the newline to act as a separator here.

Adding parentheses helps, and putting it on one line shows that it is evaluated as (X && (Y |> Z)), where X and Y are boolean expressions and Z is any function.

Is this true? And is there a simpler way to find this out? Or better, when is whitespace a significant operator and when not?


To give another example:

let v = x |> fun x -> float x |> fun y -> true

Why is y here of type float and not of type int -> float? This may be obvious, and I sure have programmed thousands of lines that way, it even feels natural, but why?

If the only answer to give is "operator precedence and -> comes before |>", so be it. But I guess/hope there's some more academical or otherwise formal though behind all this (and I still find it odd that && has lower prio than |>, again, I don't understand why and in that case it feels counter-intuitive).


Solution

  • Looking at https://learn.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/symbol-and-operator-reference/#operator-precedence it seems that && has higher precedence than |> so your guess is correct and you need to add parenthesis:

    let res x =
      (x * 1 = x && x * -1 = -x)
      |> ignore
    

    It looks like a new line doesn't act as a separator unless it has a let binding or fall in one of the rules listed here

    For example if you take this expression:

    let res = 
      5 + 1
        .GetType()
    

    You also get an error, because it applies the . operator to 1 not to the whole expression, so the precedence rules still holds, regardless of the newline.