Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add match! (match-bang) syntactic sugar to computation expressions [ RFC FS-1047 ] #572

Closed
5 tasks done
jwosty opened this issue May 22, 2017 · 27 comments
Closed
5 tasks done

Comments

@jwosty
Copy link
Contributor

jwosty commented May 22, 2017

Add a match! syntactic sugar to computation expressions

When writing computation expressions, especially asyncs that call my own functions returning Options, I find myself quite often binding it (via let!), then proceeding to use that variable only one time in a pattern match immediately following. It'd be really nice to eliminate the unnecessary let! and have a sort of match! that means the same thing as a let! followed by a vanilla match.

For example, this:

(val myAsyncTryGetResult: 'a -> Async<'a option>)
async {
    let! x = myAsyncTryGetResult foo
    match x with
    | Some(x) -> printfn "got some: %A" x
    | None -> printfn "got none" }

could instead be written as:

(val myAsyncTryGetResult: 'a -> Async<'a option>)
async {
    match! myAsyncTryGetResult foo with
    | Some(x) -> printfn "got some: %A" x
    | None -> printfn "got none" }

To implement this, the compiler could by default treat it the same as let! followed by match, or spit out a call to a new builder method for this construct if the builder defines it, perhaps called BindPattern. It would be identical in signature to builder.Bind.

E.g. compiler could emit

asyncBuilder.Bind(myAsyncTryGetResult foo, (fun x -> match x with ... ))

or

asyncBuilder.BindPattern(myAsyncTryGetResult foo, (fun x -> match x with ... ))

Pros and Cons

The advantages of making this adjustment to F# are

  • Code that encounters this type of annoyance would be a little cleaner
  • Non-invasive and simple

The disadvantages of making this adjustment to F# are

  • Nonzero effort required :P

Extra information

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

Affadavit (must be submitted)

Please tick this by placing a cross in the box:

  • This is not a question (e.g. like one you might ask on stackoverflow) and I have searched stackoverflow for discussions of this issue
  • I have searched both open and closed suggestions on this site and believe this is not a duplicate
  • This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:

  • This is not a breaking change to the F# language design
  • I would be willing to help implement and/or test this
  • [] I or my company would be willing to help crowdfund F# Software Foundation members to work on this
@jwosty jwosty changed the title Add match! syntactic sugar to computation expressions Add match! syntactic sugar to computation expressions May 22, 2017
@dsyme
Copy link
Collaborator

dsyme commented May 23, 2017

Looks like a duplicate of this #316

@jwosty
Copy link
Contributor Author

jwosty commented May 23, 2017

@dsyme looks like you're right, but since Joinads was rejected, could we reconsider? (e.g. reopen the older proposal or keep this one)

@cartermp
Copy link
Member

cartermp commented May 24, 2017

I think this is a better version of #316 and since Joinads were rejected it could be up for discussion.

@jwosty I'm curious about your views on this vs. writing a different builder, e.g., asyncMaybe. Not quite the same use case, but there's certainly some intersection here.

@jwosty
Copy link
Contributor Author

jwosty commented May 26, 2017

@cartermp I could see that being useful, but I'd much prefer having match!, since it'd cover a lot more cases (e.g. wouldn't be limited to just matching over option types). Using an asyncMaybe builder would also require that all the async calls you make inside return an option, which would make it very cumbersome to actually use.

@rmunn
Copy link

rmunn commented May 29, 2017

In the resolution of https://fslang.uservoice.com/forums/245727-f-language/suggestions/5663965-integrate-joinads-extension it was mentioned that that proposal was being declined "for now". Does that mean that it might be resurrected at some point in the future? If so, it might be wise to hold the match! syntax open for some unknown future date when joinads get integrated.

However, if the decision was that joinads would never be integrated, then there's no issue with re-evaluating #316 here.

@ploeh
Copy link

ploeh commented May 29, 2017

@cartermp While one could write a builder for each monad stack one needs, I find myself repeating that let!/match idiom over and over again, even in monad stacks (like asyncMaybe, or asyncEither, etc.).

I often pattern match on domain models, not just on built-in types like Option or Result. An example could be this tennis game state:

type Score =
| Points of PointsData
| Forty of FortyData
| Deuce
| Advantage of Player
| Game of Player

As a 'naked' discriminated union, it's not monadic (it's not even a functor), so I'm not sure it's possible to write a computation expression builder for such a type. You might still be able to write one for a combination that involves a proper monad like async, but that's a lot of boilerplate code required for each discriminated union.

It's possible that I could write a computation expression builder for e.g. Async<Score>, but I haven't tried... Even if possible, I'd also need to write one for Maybe<Score>, Async<Point>, Maybe<Point, and so on. If that's what it takes to avoid writing let! followed by match, I'll take the let!/match combo any time, but a match! keyword would be really nice.

@jwosty
Copy link
Contributor Author

jwosty commented May 29, 2017

@rmunn ah, didn't catch that joinads was rejected "for now." Therefore the disadvantage to implementing this suggestion is that it's pretty incompatible, thus if it were implemented, it would prevent Joinads from becoming a thing ever. I wonder if that's possible to avoid somehow (if Joinads is a possibility eventually).

@jwosty jwosty changed the title Add match! syntactic sugar to computation expressions Add match! (match-bang) syntactic sugar to computation expressions Feb 11, 2018
@jwosty
Copy link
Contributor Author

jwosty commented Feb 28, 2018

Is there anything preventing this from being marked as approved-in-principle?

@realvictorprm
Copy link
Member

I'm interested in a statement too. What are the real cons of this? For me it looks like a nice-to-have addition :thinking_face:

@dsyme
Copy link
Collaborator

dsyme commented Mar 2, 2018

Marking as approved-in-principle per discussion in language-design office hours

@dsyme says:
Yes, I think this can be marked approved-in-principle.

The main hesitation has been that I believe we should also add a more general construct that allows binding within the middle of control-flow expressions, including "a && b", "a || b" and so on, when those are used in control-flow position, e.g. "if bind a || bind b then ..." where the semantics is the same as "await" in async. The CE builder could name the construct so it would be called "await" in async { ... }. I thought this was recorded as a suggestion but I can't find it yet in a quick search.

This second one is in a way more general since "match bind x with... " would be the same as "match! x with ...".

But the second proposal fits perfectly well with the first - I don't think people would mind the shorter syntax even if "bind" were available.

@boloutaredoubeni
Copy link

boloutaredoubeni commented Mar 2, 2018

From language design office hours

@dsyme Re this:

In terms of bind x || bind y and in the context of async, would this work similarly to how Async.Choice works?

No. It would just be similar in effect to C# await desugaring (though that has to be more general since await is allowed anywhere in the expressions, which is not something I feel we need to do)

let! part1 = bind x in if part1 then ... else let! part2 = bind y in if part2 then ... else ...

The problem of course is that duplicates code (the then... branches), and we'd have to deal with that

@dsyme
Copy link
Collaborator

dsyme commented Mar 2, 2018

From Language Design Office Hours:

John Wostenberg [7:01 PM]
Happy to see match! approved (as seen at #572)!
As for details, any thoughts on the specific implementation of what the compiler would emit?

@dsyme says:
I'm 100% sure it should simply be short hand for "let! x = y in match x with ..."

@Rickasaurus
Copy link

Oooh man, does this mean we're going to get idris-style inline bind? That would be so rad.

@jwosty
Copy link
Contributor Author

jwosty commented Mar 5, 2018

@Rickasaurus I'm not familiar with Idris; what's inline bind?

Edit: Ah, I see what you're referencing. There's no suggestion for that feature yet -- nobody is stopping anyone from writing one! ;)

@Rickasaurus
Copy link

@jwosty this stuff http://docs.idris-lang.org/en/latest/tutorial/interfaces.html#notation

@jwosty
Copy link
Contributor Author

jwosty commented Mar 5, 2018

@Rickasaurus That's a cool language feature!

@Rickasaurus
Copy link

@jwosty I know right? Idris got it like five years ago and I've been drooling over it ever since.

@jwosty
Copy link
Contributor Author

jwosty commented Mar 5, 2018

@Rickasaurus I'm considering opening a feature request for this later. Syntactic sugar FTW!

@jwosty jwosty changed the title Add match! (match-bang) syntactic sugar to computation expressions Add match! (match-bang) syntactic sugar to computation expressions [ RFC FS-1047 ] Mar 6, 2018
@dsyme dsyme added the ready label Mar 14, 2018
@dsyme
Copy link
Collaborator

dsyme commented May 29, 2018

Implementation has been submitted

@dsyme dsyme closed this as completed May 29, 2018
@et1975
Copy link

et1975 commented Sep 12, 2018

Just found that match! doesn't allow assigning the results, ie:

let x = match! asyncX with Some v -> v | _ -> failwith "unexpected"

results in:

This construct may only be used within computation expressions

and the simplest valid code seems to be:

let! x = async { match! asyncX with Some v -> return v | _ -> return failwith "unexpected"}

which is suboptimal. Would it be possible to improve this?

@cartermp
Copy link
Member

I certainly have no objections to allowing assignment of the result. @jwosty, would you have any concerns?

@dsyme
Copy link
Collaborator

dsyme commented Sep 17, 2018

Just found that match! doesn't allow assigning the results, ie:

This is by-design, and nothing to do with match!. The right-hand-side of a let x = is not a computation expression and can't use any computation expression machinery.

For example

async { 
    let x = 
        if a then 
            let! y = ...
            y+y
        else
            let! z = ...
            z + z
    ...
}

does not compile, you must wrap the r.h.s. of the let! in an async { ... }

@cartermp
Copy link
Member

I think the unexpected bit is that because it's syntax sugar for this:

let asyncX = async { return Some 12 }
async {
    let! x = asyncX
    let res = 
        match x with
        | Some v -> v
        | None -> 12
    return res
}

folks like @et1975 would expect to assign the result.

@et1975
Copy link

et1975 commented Sep 17, 2018

The right-hand-side of a let x = is not a computation expression and can't use any computation expression machinery.

Right, but the right-hand side of let! is, hence the surprise :)

Specifically, in this context: let! (Some x) = asyncX match works exactly as expected, but addressing the compiler warning unfurls the right hand side to that... monstrosity.

@dsyme
Copy link
Collaborator

dsyme commented Sep 18, 2018

@et1975 Yes, it's somewhat large transition. So

let (Some x) = syncX

becomes

let x = match syncX with Some x -> x | None -> fail() 

and

let! (Some x) = asyncX

becomes

let! x = async { match! asyncX with Some x -> return x | None -> fail() }

I suppose we could in theory allow ! computations on the r.h.s. of a let and I don't think it would be a breaking change - just a little odd because the let is really a bind. We should make another issue on that and it would at least be interesting to explore that.

@cartermp cartermp added this to the F# 4.5 milestone Jan 21, 2019
@bizmonger
Copy link

Can we also apply that to the function syntax as well?

Example:

              storageTable
               |> createEntityAsync entity
               |> function!
                  | Error msg -> ...
                  | Ok _ -> ...

@abelbraaksma
Copy link
Member

@bizmonger, i believe that was considered, but eventually not included, but I don't remember the reasoning. You should probably open a new language suggestion, a closed one is not likely to be picked up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests