Implicit yields have a lot of trouble with unit sequences.
let g(f:unit->'a) = [ if true then f(); f() ]
let l = g id // l = [ (); () ]
let m:unit list = [ if true then id(); id() ] // needs type annotation; then m = []
The fact that l and m are different seems very wrong to me.
let n:unit list = [ if true then () else ()] // needs a type annotation??? // n = [] ???
let o:unit list = [ () ] // needs a type annotation??? // o = [ () ]
n should equal [ () ], not [], and should not need a type annotation. And o should not need a type annotation.
let o:int list = [ () ] gives a type error, which is sensible, but incompatible with the spec for implicit yields.
I would really like a way to switch off implicit yields in F# as they make the language logically inconsistent, in a way that does not appear to be fixable.
I may need some help in understanding your concerns here. One section at a time:
First snippet:
let g(f:unit->'a) = [ if true then f(); f() ]
let l = g id
let m = [ if true then id (); id () ]
LangVersion to 4.6: fails to compile g and m, as expected
LangVersion to 4.7 (default):
m gives a value restriction unless you annotate itYou could argue that m should not give a value restriction.
Second snippet:
let n = [ if true then () else ()]
let o = [ () ]
LangVersion to 4.6:
n, as expectedo compiles as unit list, as expectedLangVersion to 4.7:
n gives a value restriction unless you annotate ito compiles as unit list, no type annotation is requiredAs with the previous example, you could argue that n should not give a value restriction. o does not require a type annotation as you've described.
This code never compiled prior to F# 4.7, so I'm not sure how this is a breaking change. Do you have an example of how this breaks code that used to compile?
Third snippet:
let o:int list = [ () ]
LangVersion to 4.6: Fails to compile with a type error, as expected
LangVersion to 4.7: Fails to compile with a type error
There's nothing in the spec that says or implies that o shouldn't give a type error. Where are you reading that?
I think there's arguably a bug in giving a value restriction, forcing you to annotate things, but I'm struggling to understand what is logically inconsistent and where a breaking change is.
I think the complaint is that it's surprising that the expression is parsed differently when the type is unit:
> let f = fun _ -> 'a' in [if true then f (); f ()] : char list;;
val it : char list = ['a'; 'a']
> let f = fun _ -> 3 in [if true then f (); f ()] : int list;;
val it : int list = [3; 3]
> let f = fun _ -> () in [if true then f (); f ()] : unit list;;
val it : unit list = []
Similarly, [if true then 3 else 3] is the same as [3], which makes sense because if true then 3 else 3 is the same as 3. But [if true then () else ()] is evaluated as the empty list [].
Sorry I don't know how to select an F# version to compile with. This did limit my testing. I assumed that [ if true then () else ()] and if true then (); ()] compiled in F# before 4.7 and equalled [()]. Some of these issues may have to do with yields and not implicit yields.
What's the easiest way to test a few lines of code with F# 4.6?
In your .fsproj file add inside the PropertyGroup element:
<LangVersion>4.6</LangVersion>
@charlesroddie Some concerns/drawbacks for the addition of a type-directed rule were mentioned in the RFC https://github.com/fsharp/fslang-design/blob/master/FSharp-4.7/FS-1069-implicit-yields.md, I believe your concerns correspond to those.
I do agree that the use of a type-directed rule is less than ideal in the language. However my decision in balance was that that it is a reasonable tradeoff in practice - the utility in dropping the yield in the case of list/seq/computation expressions for view descriptions was just too compelling.
We should probably move this to a discussion thread associated with the RFC
I think there's definitely an improvement to be made regarding the value restriction here.
Normal value:
let x = if true then () else () // Compiles as 'unit'
Making it a list with yields:
let x = [ if true then yield () else yield () ] // Compiles as 'unit list'
Removing the yields (the more natural way to "listify" the value)
let x = [ if true then () else () ] // ERROR: value restriction, inferred as 'obj list'
And since this doesn't happen with other values, this should be resolved so that the following can compile:
let x = [ if true then 3 else 3 ]
let y = [ if true then () else () ]
And y in the above should evaluate to [()], not []. I think this is more bugfix-level stuff that shouldn't necessarily involve core language design.
let x = [ if true then () else () ] // ERROR: value restriction, inferred as 'obj list'
How do you get the value restriction error? I can just compile this, though the resulting type for x is indeed wrong, it says obj list. This is with 16.3.1, so perhaps a newer version introduced a change here.
Same is true for that other snippet: let m = [ if true then id (); id () ]. It compiles without error for me, but with obj list as opposed to unit list.
In both cases, annotation with : unit list gives the proper type.
When running the code, each list is empty, but as you, I would expect this to be [()] and [();()] respectively.
Rather than opening a new issue I'd like to ask about the following code:
[ "1"; if v then "2"; "3" ];;
if v is false this returns [ "1" ]. This case seem to be ambiguous by design. The question is: Is there a single line way to return [ "1"; "3" ] instead? Braces don't work:
> [ "1"; (if v then "2"); "3" ];;
[ "1"; (if v then "2"); "3" ];;
--------^^^^^^^^^^^^^
stdin(9,9): error FS0001: This expression was expected to have type
'unit'
but here has type
'string'
@matthid, the ambiguity you mention may actually be a bug, as it is not consistent:
Seems like a viable work-around, but this will never return 3:
```f#
[1; if v then yield! [2] else yield! []; 3]
The following surprisingly throws an error that you cannot use `yield!` unless inside a list. seq or array expression?!?!?
```f#
[1; (if v then yield! [2] else yield! []); 3]
The following works like you intended and always returns 3, but using in basically makes it a compound expression (even though it is one line ;).
f#
let x = if v then [2] else [] in [1; yield! x; 3;]
If the syntax were consistent, unambiguous and orthogonal, I would expect the ; to close the previous statement, and not become part of the else clause. Or, if also consistent, it would _always_ become part of the previous statement unless it is a single item, in which case yield! x; 3; would yield x and not 3. While that'd be unfortunate, at least it would be consistent with the way if is treated.
I'm rather surprised to find that:
; has lower precedence than if, meaning the if ought to be evaluated as a whole before ;)....else yield! []; 3] seems to violate type constraints. If yield! is applied to []; 3, this boils down to 3, on which yield! cannot be applied. I may analyze this wrong, but there seems to be something fishy going on here.I think if we would just take the precedence rules as a guide, your statement above should be valid and 3 should always be returned. @dsyme, @cartermp: it seems like both the implicit and explicit yields wreck the precedence of ; vs if here? Solving the precedence rules as a bug would solve @matthid's example (which only leaves the parenthesized expr oddity).
Just in case the edit didn't send out notifications /cc @dsyme @cartermp
Personally I don't think it is necessarily a bug, but definitely an oddity.
I also love how [ "1"; if v then begin "2" end; "3" ];; doesn't work as expected either, at least that is consistent. Is there any verbose syntax to "end" the if?