fsharp / fslang-suggestions

The place to make suggestions, discuss and vote on F# language and core library features
340 stars 21 forks source link

Allow typed bindings in CE `let!`s without parentheses #1329

Open baronfel opened 6 months ago

baronfel commented 6 months ago

I propose we

allow CE let-bindings to accept typed patterns without required parenthesis:

async {
  let! value: int = 
      async {
        return 10
      }
   return value + 1
}

The existing way of approaching this problem in F# is

to wrap the type pattern in parenthesis:

async {
  let! (value: int) = 
      async {
        return 10
      }
   return value + 1
}

Pros and Cons

The advantages of making this adjustment to F# are

it removes a syntactical foot-gun that is frustrating and confusing to users.

The disadvantages of making this adjustment to F# are

it is a slight special casing of the allowable patterns in CE bindings and so may make the parser/lexer/checker more complicated

Extra information

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

Related suggestions:

Affidavit (please submit!)

Please tick these items by placing a cross in the box:

Please tick all that apply:

For Readers

If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.

brianrourkeboll commented 6 months ago

It's interesting how these two examples are currently represented differently in the AST—compare the SynBinding's headPat, returnInfo, and expr:

let (x : int) = 3

https://fsprojects.github.io/fantomas-tools/#/ast?data=N4KABGBEAmCmBmBLAdrAzpAXFSAacUiaAYmolmPAIYA2as%2BEkAxgPZwWQ2wAuYAFAA8w2FDwCUYALxgAzHgKRYggA5Vk0CjwBOAV1ggAvkA

let x : int = 3

https://fsprojects.github.io/fantomas-tools/#/ast?data=N4KABGBEAmCmBmBLAdrAzpAXFSAacUiaAYmolmPAIYA2as%2BEkAxgPZwWQ2wAuYAHmGwo%2BAXjABmPAUix%2BAByrJoFHgCcArrBABfIA

Separately, there are also several expression contexts where an unparenthesized type annotation is currently disallowed that I came across in https://github.com/dotnet/fsharp/pull/16079:

https://github.com/dotnet/fsharp/blob/b077e0912d36d3d8114387369f01c9f78fc1e605/src/Compiler/Service/ServiceAnalysis.fs#L1236-L1259

I have neither looked into the compiler nor reasoned from first principles as to why that might be for each case, but if somebody looks at addressing this issue, it may be worth looking into those as well.

At least for some other pattern contexts there are good reasons why parentheses are and must be required, since, e.g., a trailing ->, |, or & token could otherwise be interpreted as part of the type:

fun (x : …) -> …
function (x : …) -> …
match … with (x : …) -> …
(x : int) | x
(x : int) & y