Skip to content

Consider allowing implicit yield in lists and arrays #643

@dsyme

Description

@dsyme

F# list and arrays (but not sequences and computation expressions) allow both data and computations that produce data, e.g.

    [ 1; 2 ]
    [ yield 1; yield 2 ]
    [ yield 1; yield 2; match a with A -> yield 1 | B -> () ]

but not

    [ 1; 2; match a with A -> yield 1 | B -> () ]

This means that adding a single computation to an otherwise simple data list requires you to add yield everywhere. This particularly bites computed views in Fable/Elmish.

It is possible we could be more tolerant here and interpret expressions in the list which are not unit-typed and are at the fringes of control-flow expressions as an implicit yield. This would allow

    [ 1; 2; match a with A -> 1 | B -> () ]

Note that the yield would, I think, still be required in the case of in element that is a control construct match, let, if etc.

It's possible that making such a more liberal interpretation can confuse people in other ways or be a breaking change. For example,

  1. the branches of the match no longer match in type - one is unit-typed and the other is not. This means the expression can't be extracted into a function However this is already the case - if the expression syntax contains yield then it is part of a generator, and is not an expression.
  2. existing code might already contain non-unit-valued items that are currently being ignored (with a warning). In this new interpretation these would now be yielded.
  3. Lists of unit values would not satisfy this

We would also need to consider whether the construct also applies to sequence expressions (implicit yield) and computation expressions (implicit yield/return).

Example

Consider this:

                    div [ClassName ("form-group has-feedback " + authorStatus) ] [
                         yield div [ClassName "input-group"][
                             yield span [ClassName "input-group-addon"] [span [ClassName "glyphicon glyphicon-user"] [] ]
                             yield input [
                                     Key ("Author_" + model.NewBookId.ToString())
                                     HTMLAttr.Type "text"
                                     Name "Author"
                                     DefaultValue model.NewBook.Authors
                                     ClassName "form-control"
                                     Placeholder "Please insert authors"
                                     Required true
                                     OnChange (fun (ev:React.FormEvent) -> dispatch (AuthorsChanged !!ev.target?value))]
                             match model.AuthorsErrorText with
                             | Some e -> yield span [ClassName "glyphicon glyphicon-remove form-control-feedback"] []
                             | _ -> ()
                         ]
                         match model.AuthorsErrorText with
                         | Some e -> yield p [ClassName "text-danger"][str e]
                         | _ -> ()
                    ]

Note that the presence of one match requires adding lots of yield. This would become:

                    div [ClassName ("form-group has-feedback " + authorStatus) ] [
                         div [ClassName "input-group"][
                             span [ClassName "input-group-addon"] [span [ClassName "glyphicon glyphicon-user"] [] ]
                             input [
                                 Key ("Author_" + model.NewBookId.ToString())
                                 HTMLAttr.Type "text"
                                 Name "Author"
                                 DefaultValue model.NewBook.Authors
                                 ClassName "form-control"
                                 Placeholder "Please insert authors"
                                 Required true
                                 OnChange (fun (ev:React.FormEvent) -> dispatch (AuthorsChanged !!ev.target?value))]
                             match model.AuthorsErrorText with
                             | Some e -> yield span [ClassName "glyphicon glyphicon-remove form-control-feedback"] []
                             | _ -> ()
                         ]
                         match model.AuthorsErrorText with
                         | Some e -> yield p [ClassName "text-danger"][str e]
                         | _ -> ()
                    ]

Pros and Cons

The advantages of making this adjustment to F# are clearer code in the common case

The disadvantages of making this adjustment to F# are

  • it's a corner-case breaking change
  • it would require a type-directed rule
  • yield! or return! would still be required to yield another computed thing. Forgetting to add yield! or return! might be even more confusing than it already is

Extra information

Estimated cost (XS, S, M, L, XL, XXL): S

cc @forki

Affidavit (please submit!)

Please tick all that apply:

  • This is not a breaking change to the F# language design

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions