Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Indentation change after if-else expression not taken into account?

Tags:

f#

Given this this operator for evaluation of side effects in pipelines

let inline (|>!) a f = f a ; a 

and this code snippet

if 1 = 1 then  
    "same"
else 
    "different"  
|>! printfn "The numbers are %s." 
|>  printfn "Yes, they are %s." 

This never prints The numbers are same but it does print Yes, they are same.

Why is the side effect operator |>! ignored here, but the |> taken into account, despite having the same indent?

Would I have to define the side effect operator differently?

written like this it works as expected.

if 1 = 1 then   "same"
else "different"  
|>! printfn "The numbers are %s." 
|>  printfn "Yes, they are %s." 

Is it only unintuitive for me that the code actually behaves as if written

if 1 = 1 then  
    "same"
else 
    "different"  
    |>! printfn "The numbers are %s." // with indent here
|>  printfn "Yes, they are %s." 

Edit:

see also https://github.com/fsharp/fslang-suggestions/issues/806 for an answer.

like image 582
Goswin Rothenthal Avatar asked Jan 24 '23 15:01

Goswin Rothenthal


1 Answers

This is not a bug, and it doesn't happen specifically for operators longer than two characters. This is a funny consequence of F#'s permissive offset rules.

When aligning lines of the same nestedness level, they have to be at the same indentation, like this:

let foo =
     bar
     baz
     qux

But this is not allowed:

let foo =
     bar
      baz  // Indented too much to the left
     qux

And neither is this:

let foo =
     bar
    baz  // Indented too little
     qux

When dealing with constructs that create nested blocks, such as if/then, this rule is used to determine when the block is over: it's when the indentation alignment is broken.

let x =
     if 1 = 1 then
       bar
       baz
     qux

But there is an exception to this rule: if the line starts with an operator, then it is allowed to be shifted to the left by at most the operator size plus 1 characters, and it will still be considered to be at "current" indentation.

For example, this works:

let x =
       1
     + 2
     + 3

But when you have operators of different sizes, it gets tricky. This works:

let x =
       1
     + 2
     + 3
    <> 6

But this doesn't:

let x =
        1
     +  2
     +  3
     <> 6

👆 this doesn't work because 2 and 3 are shifted to the left by more than the operator size plus one character.


So this is what happens in your case:

  • The first printfn is considered to be part of the else block, because it happens to align exactly with "different", but shifted to the left by operator size plus one.
  • The second printfn, however, is shifted to the left by more than operator size plus one, so it's no longer part of the else.
  • But it still happens to be correct syntax, because now it can be part of the surrounding block, with the whole result of if/then/else piped into it.
  • Normally you'd get a syntax error, like in my example with 1+2+3 <> 6 above, but in this case the syntax happens to align just right (or, perhaps, just wrong).

You can verify this by removing the extra space in front of the second printfn:

if 1 = 1 then  
    "same"
else 
    "different"  
|>! printfn "The numbers are %s." 
 |> printfn "Yes, they are %s." 

Now the second printfn will be part of the else, and you'll get an error: types string and unit don't match. This is because then returns a string, but else now returns a unit. This can be fixed by amending the then part:

if 1 = 1 then  
    ()
else 
    "different"  
|>! printfn "The numbers are %s." 
 |> printfn "Yes, they are %s." 

Now this compiles and doesn't print anything. And if you replace 1 = 1 with 1 = 2, it will correctly print "different" twice.

Finally, if you want the whole if/then/else block to be piped through both printfn calls, you have to break the alignment of "different" with the first printfn somehow. One way to do it you have offered yourself: putting "different" on the same line with else. Another way would be to indent "different" a bit further, so it no longer aligns with the first printfn:

if 1 = 1 then  
    "same"
else 
      "different"  
|>! printfn "The numbers are %s." 
|>  printfn "Yes, they are %s." 
like image 71
Fyodor Soikin Avatar answered May 17 '23 09:05

Fyodor Soikin