F# RFC FS-1069 - Implicit yields
The design suggestion Implicit yields has been marked "approved in principle". This RFC covers the detailed proposal for this suggestion.
- Approved in principle
This allows implicit
yield in list, array, sequence and those computation expressions supporting Yield/Combine/Zero/Delay.
This makes F# a nicer "templating" language. This applies especially for "views" in F# frameworks such as Fable and Fabulous.
The feature uses a type directed rule to detect side-effect statements such as
printfn inside a computation expression,
and is subject to some possible corner-case backwards-compat concerns, discussed below.
let view onLogout (model:Model) = div [ centerStyle "row" ] [ yield viewLink Page.Home "Home" if model <> None then yield viewLink Page.WishList "Wishlist" if model = None then yield viewLink Page.Login "Login" else yield buttonLink "logout" onLogout [ str "Logout" ] ]
let view onLogout (model:Model) = div [ centerStyle "row" ] [ viewLink Page.Home "Home" if model <> None then viewLink Page.WishList "Wishlist" if model = None then viewLink Page.Login "Login" else buttonLink "logout" onLogout [ str "Logout" ] ]
This has a benefit that some irregularities in incrementally adjusting F# code are ironed out. For example if you start with
[ "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday"]
and want to make the last two conditional, then with the feature on you just do this:
[ "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" if includeWeekend then "Saturday" "Sunday"]
Without implicit yields you have to scatter
yield across the expression:
[ yield "Monday" yield "Tuesday" yield "Wednesday" yield "Thursday" yield "Friday" if includeWeekend then yield "Saturday" yield "Sunday"]
Implicit yields are activated for
List, array and sequence expressions that have no explicit yield (i.e. you can't mix implicit and explicit yields). Using
Computation expressions that have no explicit
yieldand where the builder supports the neessary methods for yielding, i.e.
When implicit yields are activated,
for a construct
expr1; expr2in a computation,
expr1is checked without an expected type (as today). If the resulting type of the expression unifies with
unitwithout warning, then
expr1is interpreted as a sequential expression. Otherwise, it is interpreted as an implicit yield.
similarly, for non-computation construct
exprin a leaf position of a computation expression,
expris checked without an expected type (as today). If the resulting type of the expression unifies with
unitwithout warning, then
expris interpreted as the expression followed by yielding no results. Otherwise, it is interpreted as an implicit yield of a singleton result.
There is no corresponding "implicit return".
Possible compat concerns
Some existing F# code generates a warning when values are ignored. For example, currently:
[ 1; yield 2 ]
[ 1; yield 2 ] ------^ stdin(1,7): warning FS0020: The result of this expression has type 'int' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'.
Because an explicit yield is present, this continues to generate a warning saying the
1 is ignored and
discarded (the expression is treated like a statement).
There is still a (presumably very rare) backwards compat concern for cases where values are currently
being ignored/discarded and the list/array/sequence/computations only uses
yield!. For example
[ doSomethingThatReturnsAValueButCurrentlyDicardsIt(); yield! someThingsToYield() ]
In this case, implicit yields are activated because there is no explicit
yield. However the
doSomethingThatReturnsAValueButCurrentlyDicardsIt() would now be interpreted as a yield. This is
likely to give rise to a type error (it's unlikely that the function returns the same element type as
but if there is no type error it will yield an additional element.
The intitial working assumption is that such cases will be extraordinarily rare - given that the code reports a warning today.
For this reason, in the balance it seems ok to change the interpretation of these cases, subject to
This feature may cause confusion about what is a
yieldand what is a side-effecting operation
This feature still requires the use of
yield!and the loss of symmetry between
yield!may make the use of these features confusing for beginners.
Response: belief is that
yield!is rare, and the benefits of clarity from implicit yield are greater than the potential for loss of symmetry.
The use of a type-directed rule may cause problems
The decision to allow implicit yields only when there is no explicit yield means adding a single
yieldto a computation expression may cause a substantial "non-incremental" change in the interpretation and checking of the construct.
The backwards compat corner cases mean that a
/langversion:5.0flag is needed. Adding this flag is plan-of-record but it still needs to be done, and must be done carefully.
The main alternative is "don't do this" and continue to require explicit
This is a non-breaking change.