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.
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:
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.printfn
, however, is shifted to the left by more than operator size plus one, so it's no longer part of the else
.if
/then
/else
piped into it.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."
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With