Open smoothdeveloper opened 5 years ago
The tricky thing about this is that it's ambiguous if you mean to actually do
more things after the semicolon or not. I imagine something could be updated to disambiguate. We'd be fine accepting a PR that does this.
@cartermp, thanks for the feedback.
For me, this code sample fails the principle of least surprise, but I lack the context as to what the/an ambiguous cases would look like.
What would be ambiguous if my code sample was compiling?
Or do you have any example of something which compiles today but potentially not to the same thing if my example was compiling.
Do we agree that a change done to make my sample compile would better respect the intent of the specification?
Can we have a tag for specification ambiguity or something akin to it?
Sorry, I think I'm mistaken in calling this an ambiguity. I was referring more to user behavior, but that's not really meaningful here.
I think that your sample generally should be as you intend it. That is, do! <comp-expr>; return <expr>
should compile and execute as if these were on two separate lines. Where I think it gets weird is when you want to do!
multiple things as a semicolon-delimited list of expressions.
This isn't well-specified IMO, or if it is, then the implementation is kind of wonky, since today a warning is emitted suggesting to ignore
things, though that is arguably also not needed, since you'd only ever do this for things that don't actually return things. So would a warning get emitted for the expression that follows the do!
? Consider this example:
let f x = async { return 12 } |> Async.Ignore
let g =
async {
do! f 12; f 13
}
let h =
async {
do! f 12
return 13
}
Today you get a warning. And if h
were modified as per your example, it doesn't compile. Should the same warning get emitted if h
were to compile if it used a semicolon? Or should there be no warning? I don't think this is really clear in the spec.
@cartermp, I think there is something I had wrong intuition about:
for i in 1..2 do printfn $"{i}";printfn $"{i}";
My expectation would be that the second printfn statement falls out of the scope of the one liner for loop.
This expectations stems from C derived languages where you can omit braces and have the first semicolon ended statement terminate the block.
In that frame, I gather that my original code would desugar into
let nok = builder {
do!
z;
return 1
}
Which indeed yields the same as the one liner.
So I'm not sure how we can improve on this, beside finding a better way to guide people that trip on this in the error message.
The trouble, as noted on Slack, is how the grammar slices - and while it can be defined as 'ambiguous' - it's perhaps more that the spec isn't explicit about the precedence of rules (supposing it should and needs to be).
Considering the expression:
for i in 1..2 do printfn $"{i}";printfn $"{i}";
The trouble is in the question of where do the expressions begin and end.
a for
expression is of the form for ident in expr do expr
(actually a slightly different grammar, but roughly equivalent for our purposes here)
and a sequence expression
is of the form expr1;expr2
And this is the crux of things. According to the layout in the spec, a for
expression is listed lower than a sequence
expression - and it's not immediately clear to my reading whether this actually means by spec that it has lower logical precedence. But either we have a sequence expression, the first expression of which is our for loop; or we have a for loop, whose inner pattern is a sequence expression.
With the admittedly small chance that an upgrade to the spec will help the majority of users (who likely don't dig into the spec to understand warnings) - perhaps a more universal solution would be to try to improve error messaging around this particular error in the case that semicolons have been explicitly used, rather than implicitly inserted by the compiler.
I ran into the same issue as @smoothdeveloper and luckily found this discussion. Reading through the above, I believe the issue is about more than just a possibly ambiguous (or not sufficiently explicit) spec or the need for better error messages. With expressions, I always have the option to use one or multiple lines.
for i in 1..2 do
printfn $"{i}"
printfn "hi";
is the same as
for i in 1..2 do printfn $"{i}"; printfn "hi";
and
for i in 1..2 do
printfn $"{i}"
printfn "hi";
is the same as
(for i in 1..2 do printfn $"{i}"); printfn "hi";
I would expect the same to be possible for computation expressions. However, while
let a =
async {
do!
()
Async.Sleep 0
}
can be rewritten as
let a =
async {
do! (); Async.Sleep 0
}
there seems to be no way to rewrite
let c =
async {
do! Async.Sleep 0
return 0
}
in a single line. Parenthesis don't work. Trying
let c =
async {
(do! Async.Sleep 0); return 0
}
results in compiler error 792.
Curiously, the semantics of computation expressions given here provides do! expr in cexpr
as the syntax for this. But trying this just gives an "error FS3156: Unexpected token 'in' or incomplete expression".
This SO question looks similar, although it's a let!
and the compiler diagnostic is different.
Given this code:
Actual behavior
the
return
expression innok
gives:This seems like a fallback message when failing to parse the CE.
Expected behavior
I'd expect it to work fine, but probably missing something in the spec, those are the points making me feel it should work:
Regarding semicolon §6.5.2 :
Regarding computation expressions §6.3.10:
If this is not supposed to work, is it possible to know which area of the spec would explain it?
Known workarounds
Related information