Stream: ideas

Topic: `match` without `->`


view this post on Zulip Richard Feldman (Feb 20 2025 at 23:15):

what if we made match consistent with if and have it just use curly braces instead of a special -> and , thing? e.g.

...we do this:

    match a {
        Blue { 47 }
        Green { 19 }
        Red { 12 }
        lower { 1 }
        [1, 2, 3, .. as rest] { 123 }
        3.14 { 314 }
    }

view this post on Zulip Richard Feldman (Feb 20 2025 at 23:15):

seems like it's just as easy to read, it's one less piece of syntax to learn, and if you want a multiline branch you're already all set up with the curly braces

view this post on Zulip Brendan Hansknecht (Feb 20 2025 at 23:28):

Feels super weird at first look, but I'm sure that is a first look bias. Also, my brain still thinks in WSA where it looks exceptionally broken.

view this post on Zulip Brendan Hansknecht (Feb 20 2025 at 23:29):

Still feels kinda alien though.

view this post on Zulip jan kili (Feb 20 2025 at 23:47):

Kind of smooth, and definitely aligned with other changes. The current match syntax could be an outlier alongside brace blocks.

view this post on Zulip Anthony Bullard (Feb 20 2025 at 23:47):

Here's an updated sample with tuples (and records):

match_time = |a| match a {
    Blue { 47 }
    Green { 19 }
    Red { 12 }
    lower { 1 }
    [1, 2, 3, .. as rest] { 123 }
    3.14 { 314 }
    (1, 2, 3) { 123 }
    { foo: 1, bar: 2 } { 12 }
}

The last two (especially the last one) make me feel a little squeamish

view this post on Zulip jan kili (Feb 20 2025 at 23:48):

I wonder if this would benefit from an additional per-branch keyword, like an on prefix.

view this post on Zulip jan kili (Feb 20 2025 at 23:49):

or just a second similar use of if.

view this post on Zulip jan kili (Feb 20 2025 at 23:50):

or when.

view this post on Zulip Anthony Bullard (Feb 20 2025 at 23:51):

Or maybe just an -> ;-)

view this post on Zulip Anthony Bullard (Feb 20 2025 at 23:51):

Since for me the issue is the space between the pattern and the expression

view this post on Zulip Anthony Bullard (Feb 20 2025 at 23:52):

Like, I can read that last branch, but I think many others would struggle with it

view this post on Zulip jan kili (Feb 20 2025 at 23:54):

Are we already heading for that with braces in other ways?

if x == { foo: 1, bar: 2 } { 12 } else { 24 }

(though this would get multilined apparently)

view this post on Zulip Anthony Bullard (Feb 20 2025 at 23:56):

Yes, I think so

view this post on Zulip Sam Mohr (Feb 21 2025 at 00:09):

Seems like an arrow does the job better :woman_shrugging:

view this post on Zulip Sam Mohr (Feb 21 2025 at 00:21):

If we're already having the formatter add braces, why not have the formatter add arrows?

view this post on Zulip Sam Mohr (Feb 21 2025 at 00:23):

I think I'll already be leaning entirely on the formatter and my editor for tabs and indentation

view this post on Zulip Sam Mohr (Feb 21 2025 at 00:24):

Seems reasonable to have it add braces and arrows for me too

view this post on Zulip jan kili (Feb 21 2025 at 00:25):

I think the motivation is to avoid having a semantically-triple-overloaded symbol.

view this post on Zulip Sam Mohr (Feb 21 2025 at 00:27):

How about we steal the => that everyone else uses

view this post on Zulip Sam Mohr (Feb 21 2025 at 00:28):

Then it's only used twice

view this post on Zulip Sam Mohr (Feb 21 2025 at 00:28):

For matching and effectful function types

view this post on Zulip jan kili (Feb 21 2025 at 00:29):

I really like that that would overload both arrows in equally "whatever" ways :smiley:

view this post on Zulip jan kili (Feb 21 2025 at 00:32):

match_time = |a| match a {
    Blue => { 47 }
    Green => { 19 }
    Red => { 12 }
    lower => { 1 }
    [1, 2, 3, .. as rest] => { 123 }
    3.14 => { 314 }
    (1, 2, 3) => { 123 }
    { foo: 1, bar: 2 } => { 12 }
}

view this post on Zulip jan kili (Feb 21 2025 at 00:34):

Silly idea for completeness: tilde arrow ~>

view this post on Zulip Sam Mohr (Feb 21 2025 at 00:38):

I feel like the real learning is that whoever designed ASCII and the modern keyboard should've prioritized more useful symbols that are easy to type

view this post on Zulip Sam Mohr (Feb 21 2025 at 00:39):

That's partly why keywords work so well for languages, I think

view this post on Zulip jan kili (Feb 21 2025 at 00:40):

I often chuckle at how early Japanese adoption of emoji has forever exposed other cultures to what their snacks look like.

view this post on Zulip Richard Feldman (Feb 21 2025 at 00:53):

Anthony Bullard said:

Here's an updated sample with tuples (and records):

match_time = |a| match a {
    Blue { 47 }
    Green { 19 }
    Red { 12 }
    lower { 1 }
    [1, 2, 3, .. as rest] { 123 }
    3.14 { 314 }
    (1, 2, 3) { 123 }
    { foo: 1, bar: 2 } { 12 }
}

The last two (especially the last one) make me feel a little squeamish

the last one never really comes up in practice; I don't see people doing pattern matches on plain records (they're always wrapped in something else)

view this post on Zulip Richard Feldman (Feb 21 2025 at 00:54):

and for tuples I usually see matching on tags for one or both of the tuples, e.g.

match (names.first(), emails.first()) {
    (Ok(name), Ok(email)) { foo(name, email) }
    (_, _) { bar() }
}

view this post on Zulip Richard Feldman (Feb 21 2025 at 00:58):

some examples from Luke's roc-realworld branch

match Env.var("DB_PASSWORD") {
    Ok(password) { Password(password) }
    Err(VarNotFound) { None }
}

view this post on Zulip Richard Feldman (Feb 21 2025 at 00:59):

get_by_slug! : Articles, Str => Result (List U8) [NotFound, InternalErr Str]
get_by_slug! = |{ client, prepared }, slug|
    match ArticlesSql.get_article_by_slug!(client, prepared.get_article_by_slug, slug) {
        Ok([row]) {
            {
                slug: row.slug,
                title: row.title,
                description: row.description,
                body: row.body,
                created_at: row.created_at,
                updated_at: row.updated_at,
                favorited: row.favorited,
                favorites_count: row.favorites_count,
                tag_list: row.comma_separated_tags.split(","),
                author: {
                    username: row.author_username,
                    bio: row.author_bio,
                    image_url: row.author_image_url,
                    following: row.author_following,
                },
            })
            .encode(Json.utf8.transform(CamelCase))
            ->Ok
        }
        Ok([]) { Err(NotFound) }
        Ok([..]) { Err(InternalErr("Multiple articles found for the slug ${slug.inspect()}")) }
        Err(db_err) { Err(InternalErr(db_err.inspect())) }
    }

view this post on Zulip Richard Feldman (Feb 21 2025 at 01:01):

hm, that first branch there makes me question whether we want to always require braces, even if there's a record (separately from the arrow vs. nothing vs. alternatives question) :thinking:

view this post on Zulip Richard Feldman (Feb 21 2025 at 01:02):

like Foo { { x, y } } or Foo -> { { x, y } } or anything else has the problem of nested braces

view this post on Zulip Richard Feldman (Feb 21 2025 at 01:02):

but it feels weird to make an exception for records only, when other expressions could omit the braces too :sweat_smile:

view this post on Zulip Richard Feldman (Feb 21 2025 at 01:03):

I guess if and else branches would have the same consideration

view this post on Zulip Anthony Bullard (Feb 21 2025 at 01:14):

There is no strict reason for braces if we require commas :wink:

view this post on Zulip Richard Feldman (Feb 21 2025 at 01:22):

also if we don't haha

view this post on Zulip Richard Feldman (Feb 21 2025 at 01:23):

and it's still annoying to me how Rust has commas sometimes and braces other times

view this post on Zulip Richard Feldman (Feb 21 2025 at 01:23):

but it might be easier to think about what we should do in the case of if

view this post on Zulip Richard Feldman (Feb 21 2025 at 01:23):

and then do that too with match

view this post on Zulip Richard Feldman (Feb 21 2025 at 01:24):

because if also currently formats to always have braces, and so would also run into the if condition { { x, y } } else { { a, b } } problem

view this post on Zulip Elias Mulhall (Feb 21 2025 at 02:30):

The thing that sold me on braces is that they can be required for statements but optional/removed for expressions, so you can write a lot of code without them. This is an interesting idea but goes against that.

view this post on Zulip Sky Rose (Feb 21 2025 at 02:55):

One thing I liked about the old -> days was that for both their uses (function definition and matching), you're binding variables in a pattern and then using those bindings in the following block. So what if we changed match to use the new | | syntax so it's the same as functions?

    match a {
        | Blue | 47
        | Green | {
            x = 19
            x
        }
        | x | x
        | [1, 2, 3, .. as rest] | {
            123
        }
        | (1, 2, 3) | 123
        | { foo: 1, bar: 2 } | 12
    }

view this post on Zulip Sam Mohr (Feb 21 2025 at 02:57):

If we're going "low whitespace sensitivity", then this probably is ambiguous if a branch's body could be a pattern or an expression

view this post on Zulip Sam Mohr (Feb 21 2025 at 02:59):

match a {
    | Blue | Red | Green | Yellow
}

view this post on Zulip Sam Mohr (Feb 21 2025 at 02:59):

Is this matching 3 different colors and returning Yellow, or matching twice?

view this post on Zulip Sky Rose (Feb 21 2025 at 03:01):

Ah, that's ambiguous between | marking functions and | being an "or" in patterns.
Are you allowed to use | in the pattern in a function definition?

view this post on Zulip Sky Rose (Feb 21 2025 at 03:01):

Like, do we already have this problem?

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:01):

I don't think there's a reason we'd want to prevent that

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:02):

Though you're right that we have to be space sensitive for function args, that's unfortunate

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:02):

Unless we move to or

view this post on Zulip Sky Rose (Feb 21 2025 at 03:09):

Split that topic into a separate thread: #ideas > `|` ambiguity in function defintion

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:10):

Are we talking about function type annotations?

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:11):

The args to a function

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:11):

Because a function definition does not allow alternates

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:11):

Oh, well, that works

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:11):

It'll suck to make parsePattern have to have a flag for "disallow_alternatives"

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:11):

But I think thats' the correct choice

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:12):

Someone correct me on this, but I think it's a restricted set of patterns there

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:12):

If we go with or, I think we could allow it, right? But yes, | would not allow it for function args

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:12):

Basically idents, tags with payloads, destructures....and that's it?

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:12):

For now yes

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:13):

But yes, I think | <pattern> | with or for alternates works

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:13):

But Sam gets big mad when we bring that up :rofl:

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:13):

SAM ANGRY

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:13):

in addition to function arguments, technically everything in front of an = assignment is a pattern.

with that in mind, I think the only place patterns are restricted is that | (or or) is only allowed in match, and top-level declarations can only use identifiers (e.g. you can't do (foo, bar) = at the top level)

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:14):

I'd love if that last restriction were true

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:14):

This is valid right now:

module [x, y]

{ x, y } = { x: 1, y: 2 }

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:15):

It makes function lifting for @Isaac Van Doren hard

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:15):

Ooooo

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:15):

regarding the "matches use lambda syntax" idea - wouldn't that syntax not have the spaces inside the |s?

so:

    match a {
        |Blue| 47
        |Green| {
            x = 19
            x
        }
        |x| x
        |[1, 2, 3, .. as rest]| {
            123
        }
        |(1, 2, 3)| 123
        |{ foo: 1, bar: 2 }| 12
    }

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:15):

I mean, ewwwww

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:15):

I don't think we should allow destructures on the top-level

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:16):

Okay, that's great, we'd need the interpreter to get rid of them for function lifting to be reasonable to implement

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:16):

Richard Feldman said:

regarding the "matches use lambda syntax" idea - wouldn't that syntax not have the spaces inside the |s?

so:

    match a {
        |Blue| 47
        |Green| {
            x = 19
            x
        }
        |x| x
        |[1, 2, 3, .. as rest]| {
            123
        }
        |(1, 2, 3)| 123
        |{ foo: 1, bar: 2 }| 12
    }

I definitely think canonical style for this syntax would be no spaces

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:16):

I'm actually here for this syntax!

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:17):

I will make sure that's a parse error

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:17):

I don't hate this syntax

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:17):

I'm cool with it as long as the syntax highlighting looks like the last two branches

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:17):

I hate that I'll have to update my PR again, but.....that's life on the edge

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:17):

where the |s are highlighted like keywords, but the destructure delimiters like ( and { inside it are highlighted differently

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:17):

We'd definitely want or here, which I'm okay with

view this post on Zulip Sky Rose (Feb 21 2025 at 03:18):

And you can disambiguate ors in patterns because they do have spaces!

# 2 branches
match a {
    |Blue| Red |Green| Yellow
}
match a {
# 1 branch with 3 options
    |Blue | Red | Green| Yellow
}

(this is terrible)

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:18):

Ok, We'll have to talk to @Eli Dowling about that

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:18):

@Sky Rose luckily the formatter would add newlines for you

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:19):

I think we will go with:

match a {
    |Blue or Red or Green| Yellow
}

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:19):

I think having or in function args shouldn't be blocked, just for top-level defs

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:19):

I can't imagine how or in function args makes sense

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:19):

Can you give me an example?

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:19):

well it doesn't really make sense outside match

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:19):

like in the same way that if I write my_fn = |Ok(foo)| ... it's just not handling the possibility that the function gets passed Err

view this post on Zulip Sky Rose (Feb 21 2025 at 03:20):

Here's an example:
| Tag1(x) <or> Tag2(_, x)| do_stuff(x)

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:20):

It would make sense in something like Elixir where it's dynamic dispatch and multiple function heads and stuff like that

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:20):

get_user_email = |Admin({ email, .. }) or Guest(email)| email

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:20):

right but like

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:20):

oh wait

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:20):

so you're thinking you make it exhaustive

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:20):

yes

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:21):

huh, I'd never considered that!

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:21):

So you are destructuring multiple tags and extracting a payload member with the same type in each...

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:21):

I would never think to do that, but I guess that works

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:21):

so I guess the idea is that you would do it right there in the | instead of immediately having a match which does the same thing

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:21):

Yep

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:21):

I think that's actually pretty sweet

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:21):

yeah I like that it's one less rule to learn

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:21):

I'm thinking about how we didn't want to block val.(|arg| lambda(arg))

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:22):

it's just like "yeah you can use this wherever, you just gotta be exhaustive like always"

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:22):

Stuff just works together

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:22):

So it's like just one extra feature on top of what we currently support there

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:22):

yeah

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:22):

I'm down

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:22):

We can just parse patterns and let Can get angry about bad ones

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:22):

Not sure if guard patterns make sense haha, those aren't exhaustive

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:23):

Makes my life easier

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:23):

I'll update my PR in the morning

view this post on Zulip Sky Rose (Feb 21 2025 at 03:23):

If you don't allow this in a match or function definition, you have to have duplicate branches.

match user {
  |Admin({ email, .. })| do_stuff(email)
  |Guest(email)| do_stuff(email)
}

or maybe an extra variable assignment

email = match user {
  |Admin({ email, .. })| email
  |Guest(email)| email
}
do_stuff(email)

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:23):

And then hopefully get Record patterns done

view this post on Zulip Anthony Bullard (Feb 21 2025 at 03:24):

I'm about to turn into a pumpkin, see y'all tomorrow !

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:25):

Sam Mohr said:

This is valid right now:

module [x, y]

{ x, y } = { x: 1, y: 2 }

It makes function lifting for Isaac Van Doren hard

just to close the loop on this - back in the day when Folkert and I were working on the initial versions of these things, I remember thinking "this will be fine because we can just desugar it into the equivalent of:

$record0 = { x: 1, y: 2 }
x = $record0.x
y = $record0.y

...but then it turns out this feature sees such little use in practice that it doesn't really seem worth the trouble anytime soon

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:25):

especially because of the "ok smart guy now how do you write a type annotation for that" problem

view this post on Zulip Sky Rose (Feb 21 2025 at 03:25):

It'd be more useful in practice if you could do interesting compile-time calculations top level

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:26):

so maybe someday someone might decide to implement it just to remove the restriction, but I definitely think for 0.1.0 (and possibly indefinitely) we should just say "identifier patterns only top-level assignments"

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:26):

Sky Rose said:

It'd be more useful in practice if you could do interesting compile-time calculations top level

it's certainly possible, and now we're on a path to find out! :smiley:

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:27):

You can write a type annotation for it though:

{ x, y } : { x : Str, y : U64 }
{ x, y } = { x: "abc", y: 123 }

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:27):

right, sorry - really I mean like how does the compiler deal with that :sweat_smile:

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:27):

actually I guess it's the same desugaring thing

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:27):

yep

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:28):

oh right, there's another problem though that I'm just remembering

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:28):

I have written that code before... not clean, it was on the path to UI state management

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:28):

SomeTag(x) = get_single_tag_wrapper(y)

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:28):

that's valid, if strange

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:28):

as long as get_single_tag_wrapper returns a [SomeTag(Whatever)]

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:28):

It's pretty much all "valid, if strange"

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:29):

it has to be exhaustive, yes

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:29):

so then what even is the syntax for the type annotation there?

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:29):

[SomeTag(x)] : [SomeTag(Str)]
SomeTag(x) = get_single_tag_wrapper(y)

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:29):

Yep

view this post on Zulip Sam Mohr (Feb 21 2025 at 03:29):

Not great

view this post on Zulip Richard Feldman (Feb 21 2025 at 03:29):

yeah :sweat_smile:

view this post on Zulip Sky Rose (Feb 21 2025 at 03:29):

Before we commit to or in patterns, I wanna pitch the idea for + instead of or, but to avoid derailling this conversation, let's confine any discussion in the other thread #ideas > `+` instead of `|` in patterns

view this post on Zulip Luke Boswell (Feb 21 2025 at 04:19):

If someone could point out the final position on this, I'll update my realword PR so we can merge the syntax in

view this post on Zulip Luke Boswell (Feb 21 2025 at 04:19):

It sounds like we've got a consistent syntax for patterns in functions args and match statements

view this post on Zulip Richard Feldman (Feb 21 2025 at 04:33):

Richard Feldman said:

    match a {
        |Blue| 47
        |Green| {
            x = 19
            x
        }
        |x| x
        |[1, 2, 3, .. as rest]| {
            123
        }
        |(1, 2, 3)| 123
        |{ foo: 1, bar: 2 }| 12
    }

it's this, plus changing from | to or for alternatives in patterns

view this post on Zulip Joshua Warner (Feb 21 2025 at 04:43):

Using this lambda-like syntax makes me think something like this would be possible:

handle_guest = |Guest({email, ..})| foo(email)
handle_admin = |Admin(email)| bar(email)
some_function = |user| match user { handle_guest, handle_admin }

view this post on Zulip Luke Boswell (Feb 21 2025 at 05:18):

I updated the PR https://github.com/rtfeldman/roc-realworld/pull/1

view this post on Zulip Luke Boswell (Feb 21 2025 at 05:18):

It kind of feels a little like we're swimming in | characters now. Maybe it's the lack of syntax highlighting... but the patterns all look like lambdas

view this post on Zulip Luke Boswell (Feb 21 2025 at 05:21):

Here's an example

match (method, path) {
    |(GET, "/articles/${slug}")| auth_optional!(|opt_user_id| db.articles.get_by_slug!(opt_user_id, slug))
    |(GET, "/articles")| auth_optional!(|opt_user_id| db.articles.list!(opt_user_id, req.params()))
    |(POST, "/articles")| auth_json!(|user_id, article| db.articles.insert!(user_id, article))
    |(PUT, "/articles/${slug}")| auth_json!(|user_id, article| db.articles.update!(user_id, slug, article))
    |(DELETE, "/articles/${slug}")| auth!(|user_id| db.articles.delete!(user_id, slug))
    |(GET, "/articles/feed")| auth!(|user_id| db.articles.feed!(user_id, req.params()))
    |(POST, "/articles/${slug}/comments")| auth_json!(|user_id, comment| db.comments.create!(user_id, slug, comment))
    |(GET, "/articles/${slug}/comments")| auth_optional!(|opt_user_id| db.comments.list!(opt_user_id, slug))
    |(DELETE, "/articles/${slug}/comments/${id}")| auth!(|user_id| db.comments.delete!(user_id, slug, id))
    |(POST, "/articles/${slug}/favorite")| auth!(|user_id| db.articles.favorite!(user_id, slug))
    |(DELETE, "/articles/${slug}/favorite")| auth!(|user_id| db.articles.unfavorite!(user_id, slug))
    |(GET, "/tags")| to_resp(db.tags.list!())
    |(GET, "/profiles/${username}")| to_resp(db.users.get_by_username!(username))
    |(POST, "/profiles/${username}/follow")| auth!(|user_id| db.users.follow_username!(user_id, username))
    |(DELETE, "/profiles/${username}/follow")| auth!(|user_id| db.users.unfollow_username!(user_id, username))
    |(GET, "/user")| auth!(|user_id| db.users.get_by_id!(user_id))
    ...
}

view this post on Zulip Luke Boswell (Feb 21 2025 at 05:26):

For the common cases it looks pretty good.

match req.method {
    |"GET"| Ok(GET)
    |"POST"| Ok(POST)
    |"PUT"| Ok(PUT)
    |"DELETE"| Ok(DELETE)
    |"OPTIONS"| Ok(OPTIONS)
    |_| Err(UnrecognizedMethod(req.method))
}

match client.query!(cmd) {
    |Ok([row])| Article.fromRow(row).(Ok)
    |Ok([])| Err(NotFound)
    |Err(db_err)| Err(InternalErr(db_err.inspect()))
}

match Env.var!("LOG_LEVEL") {
    |Ok(level_str)| {
        LogLevel.from_str(level_str) ? |UnsupportedLevel|
            InitFailed("Invalid LOG_LEVEL env var: ${level_str}")
    }
    |Err(VarNotFound)| default_log_level
}

view this post on Zulip Joshua Warner (Feb 21 2025 at 05:38):

I think I like -> better at this point

view this post on Zulip Joshua Warner (Feb 21 2025 at 05:38):

Altho I do really like the idea of both that and the braces being optional and inserted automatically by the formatter

view this post on Zulip Sam Mohr (Feb 21 2025 at 05:54):

Joshua Warner said:

Using this lambda-like syntax makes me think something like this would be possible:

handle_guest = |Guest({email, ..})| foo(email)
handle_admin = |Admin(email)| bar(email)
some_function = |user| match user { handle_guest, handle_admin }

I don't think that could work since function arg patterns need to be exhaustive

view this post on Zulip Eli Dowling (Feb 21 2025 at 08:45):

Is there a reason we don't like fat arrow?

=>
It's only used in function signatures currently.

I'm gonna be honest I think pipe for match is really really visually confusing when mixed with pipe for functions.

And always having braces would just be so noisy.

I feel like a separator of some kind is a must for visual clarity

view this post on Zulip Sam Mohr (Feb 21 2025 at 08:53):

I also agree that => would be a great option still. I think |pattern| makes sense because then both function defs and pattern defs are the same shape that means "eat these args and use them to spit out this value"

view this post on Zulip Sam Mohr (Feb 21 2025 at 08:54):

So the similarity is intentionally outlined in the syntax reuse

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 08:56):

re match x { |pattern| expr }
imo, any syntax with brackets around pattern in the match arm is redundant because you have a single "argument" which is a pattern (if there are multiple patterns in the arm it becomes even weirder)

also, lack of a delimiter such as ->, reminds me the problem with (arg1, arg2) -> result vs fn(arg1, arg2, result):
(a rust based example since I'm not sure how to explain my confusion via roc syntax)
match x { pat1, pat2 => expr } vs match { pat1, pat2 , expr }

re match and lambda consistency
I always felt a bit uncomfortable about similarity of match arms and lambdas, wanting to have a syntax that would work in both cases, but it seems it's a good idea to distinguish them because of the exhaustion problem Sam mentioned

in short, I'm leaning towards status quo

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 08:59):

also, what about if guards?

view this post on Zulip Sam Mohr (Feb 21 2025 at 09:00):

If guards shouldn't be a problem

view this post on Zulip Sam Mohr (Feb 21 2025 at 09:01):

They are of the shape <pattern> if <expr> <arm-expr>

view this post on Zulip Eli Dowling (Feb 21 2025 at 09:01):

I was going to make the same complaint @Sam Mohr it feels like it would be nice if it matched lamda syntax.

This feels like a case where old roc syntax is just a clear winner:

when (method, path) is
    (GET, "/articles/${slug}") -> auth_optional! \opt_user_id -> db.articles.get_by_slug! opt_user_id, slug
    (GET, "/articles") -> auth_optional! \opt_user_id -> db.articles.list! opt_user_id, req.params
    (POST, "/articles") -> auth_json! \user_id, article -> db.articles.insert! user_id, article
    (PUT, "/articles/${slug}") -> auth_json! \user_id, article -> db.articles.update! user_id, slug, article
    (DELETE, "/articles/${slug}") -> auth! \user_id -> db.articles.delete! user_id, slug
    (GET, "/articles/feed") -> auth! \user_id -> db.articles.feed! user_id, req.params
    (POST, "/articles/${slug}/comments") -> auth_json! \user_id, comment -> db.comments.create! user_id, slug, comment
    (GET, "/articles/${slug}/comments") -> auth_optional! \opt_user_id -> db.comments.list! opt_user_id, slug
    (DELETE, "/articles/${slug}/comments/${id}") -> auth! \user_id -> db.comments.delete! user_id, slug, id
    (POST, "/articles/${slug}/favorite") -> auth! \user_id -> db.articles.favorite! user_id, slug
    (DELETE, "/articles/${slug}/favorite") -> auth! \user_id -> db.articles.unfavorite! user_id, slug
    (GET, "/tags") -> to_resp(db.tags.list!)
    (GET, "/profiles/${username}") -> to_resp(db.users.get_by_username! username)
    (POST, "/profiles/${username}/follow") -> auth! \user_id -> db.users.follow_username! user_id, username
    (DELETE, "/profiles/${username}/follow") -> auth! \user_id -> db.users.unfollow_username! user_id, username
    (GET, "/user") -> auth! \user_id -> db.users.get_by_id! user_id
    // ... existing code ...

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 09:01):

If guards shouldn't be a problem

I mean, how weird they will look like in a real app with different proposals?

view this post on Zulip Sam Mohr (Feb 21 2025 at 09:04):

Honestly, I think any unaligned match statement like the one you showed Eli is super hard to read without some form of alignment of the arms of the matches

view this post on Zulip Eli Dowling (Feb 21 2025 at 09:04):

Trying to break down why it works much better for me:
-> is 2 chars so it doesn't get lost in the soup of (|}(}}| (I seriously struggle to tell what is a ( and what is a| when they are close together.

-> is directional so I don't get confused by what is the end of a match vs what is the start of a lambda.

There is cohesion. -> means "start a new sub expression" in both matching and lambda.

view this post on Zulip Sam Mohr (Feb 21 2025 at 09:06):

I'd much rather see one of these

when (method, path) is
    (GET, "/articles/${slug}") ->
        auth_optional! \opt_user_id -> db.articles.get_by_slug! opt_user_id, slug
    (GET, "/articles") ->
        auth_optional! \opt_user_id -> db.articles.list! opt_user_id, req.params
    (POST, "/articles") ->
        auth_json! \user_id, article -> db.articles.insert! user_id, article
    (PUT, "/articles/${slug}") ->
        auth_json! \user_id, article -> db.articles.update! user_id, slug, article
    (DELETE, "/articles/${slug}") ->
        auth! \user_id -> db.articles.delete! user_id, slug
    (GET, "/articles/feed") ->
        auth! \user_id -> db.articles.feed! user_id, req.params

when (method, path) is
    (GET, "/articles/${slug}")    -> auth_optional! \opt_user_id -> db.articles.get_by_slug! opt_user_id, slug
    (GET, "/articles")            -> auth_optional! \opt_user_id -> db.articles.list! opt_user_id, req.params
    (POST, "/articles")           -> auth_json! \user_id, article -> db.articles.insert! user_id, article
    (PUT, "/articles/${slug}").   -> auth_json! \user_id, article -> db.articles.update! user_id, slug, article
    (DELETE, "/articles/${slug}") -> auth! \user_id -> db.articles.delete! user_id, slug
    (GET, "/articles/feed")       -> auth! \user_id -> db.articles.feed! user_id, req.params

view this post on Zulip Eli Dowling (Feb 21 2025 at 09:08):

I totally agree, but I do feel like it is a valid test for cases where we have dense content, sometimes it's not so easily formattable for clarity

view this post on Zulip Eli Dowling (Feb 21 2025 at 09:10):

og also has a pleasing formatting feature the arrows sit nicely at the end. vs ||:

match (method, path) {
    |(GET,    "/articles/${slug}")|   auth_optional!(|opt_user_id| db.articles.get_by_slug!(opt_user_id, slug))
    |(GET,    "/articles")|           auth_optional!(|opt_user_id| db.articles.list!(opt_user_id, req.params()))
    |(POST,   "/articles")|           auth_json!(|user_id, article| db.articles.insert!(user_id, article))
    |(PUT,    "/articles/${slug}")|   auth_json!(|user_id, article| db.articles.update!(user_id, slug, article))

view this post on Zulip Sam Mohr (Feb 21 2025 at 09:11):

It definitely feels like we're pushing for a syntax here that leads to very few syntactic primitives, which I don't think is a win in and of itself. I'd rather have more variety in syntax to indicate uniquely what I'm looking at rather than a homogeneous syntax that makes glancing harder

view this post on Zulip Sam Mohr (Feb 21 2025 at 09:12):

Why the giant gap in your example?

view this post on Zulip Eli Dowling (Feb 21 2025 at 09:13):

I formatted the whole thing and only took the first few. Fixed

view this post on Zulip Eli Dowling (Feb 21 2025 at 09:14):

for the sake of completeness:

match (method, path) {
    |(GET, "/articles/${slug}")|
        auth_optional!(|opt_user_id| db.articles.get_by_slug!(opt_user_id, slug))
    |(GET, "/articles")|
        auth_optional!(|opt_user_id| db.articles.list!(opt_user_id, req.params()))
    |(POST, "/articles")|
        auth_json!(|user_id, article| db.articles.insert!(user_id, article))
    |(PUT, "/articles/${slug}")|
        auth_json!(|user_id, article| db.articles.update!(user_id, slug, article))
}

view this post on Zulip Sam Mohr (Feb 21 2025 at 09:18):

I guess the newlines would need { and } meaning this would read a bit more clearly actually

match (method, path) {
    |(GET, "/articles/${slug}")| {
        auth_optional!(|opt_user_id| db.articles.get_by_slug!(opt_user_id, slug))
    }
    |(GET, "/articles")| {
        auth_optional!(|opt_user_id| db.articles.list!(opt_user_id, req.params()))
    }
    |(POST, "/articles")| {
        auth_json!(|user_id, article| db.articles.insert!(user_id, article))
    }
    |(PUT, "/articles/${slug}")| {
        auth_json!(|user_id, article| db.articles.update!(user_id, slug, article))
    }
}

view this post on Zulip Eli Dowling (Feb 21 2025 at 09:29):

Why? it's only a single expression still

view this post on Zulip Norbert Hajagos (Feb 21 2025 at 10:01):

I would too miss a visually distinct delimiter between the pattern and the expression. it's another thing that -> is much easier to write in my layout than =>.
We wouldn't have compleat symmetry between patterns and lambdas anyway. Making them look the same would ask of an explanation why:

All that is to say, if they are so different, there's no harm in making them look different.

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 10:10):

I'd rather have more variety in syntax to indicate uniquely what I'm looking at rather than a homogeneous syntax that makes glancing harder

oh yeah, it's also about grepability. in this sense, e.g. => in rust is very unambiguous

view this post on Zulip Norbert Hajagos (Feb 21 2025 at 10:26):

Here are some examples with guards:

|<pattern>| <guard> <expr>
It isn't visually clear where the expressions begin. And these patters aren't event that long.

match a {
    |OK(num)| if num > 3 Big
    |OK(num)| if num <= 3 Small
    |Err(_)| Other
}

This (and the formatter?) would push ppl to use braces for every case, which is an improvement, but still not as good I think.

match a {
    |Ok(num)| if num > 3 { Big }
    |Ok(num)| if num <= 3 { Small }
    |Err(_)| { Other }
}

This looks better to me, because the | delimits the pattern and expression.
|<pattern> <guard>| <expr>

match a {
    |Ok(num) if num > 3| Big
    |Ok(num) if num == 3| Small
    |Err(_)| Other
}

But don't get me started on code where we return a lambda from the match (Which idk how realistic of a use case is)

match a {
    |Ok(num) if num > 3| |num| num +100
    |Ok(num) if num == 3| |num| num + 10
    |Err(_)| |num| num
}

I don't think braces and newlines help the | pattern syntax with or without guards, since the lambdas are easy to confuse with patterns. Even using helper functions could be confused with patterns.

match a {
    |Ok(num) if num > 3| {
        add10 = |number| num +10
        // return or use it later
    }
    |Ok(num) if num == 3| {
        |num| num + 10
    }
    |Err(_)| {
        |num| num
    }
}

And here is the current version

match a {
    Ok(num) if num > 3 -> Big
    Ok(num) if num == 3 -> Small
    Err(_)  -> Other
}

view this post on Zulip Anthony Bullard (Feb 21 2025 at 11:18):

I agree with Norbert here, I think guards kind of change the calculus on this for me

view this post on Zulip Anthony Bullard (Feb 21 2025 at 11:21):

Simply removing the arrow just doesn't work. The |...| syntax could be made to work but feels much noisier to me than an arrow.

view this post on Zulip Anthony Bullard (Feb 21 2025 at 11:26):

The match syntax with -> is also almost identical to Gleam's case expression which is very readable and clear

view this post on Zulip Agus Zubiaga (Feb 21 2025 at 11:53):

Yeah, this seems way too noisy to me

view this post on Zulip Isaac Van Doren (Feb 21 2025 at 13:47):

I definitely prefer a version with an arrow to all of the options proposed so far

view this post on Zulip Richard Feldman (Feb 21 2025 at 13:50):

ok, how about this?

view this post on Zulip Isaac Van Doren (Feb 21 2025 at 13:56):

I like that!

view this post on Zulip Anthony Bullard (Feb 21 2025 at 14:07):

That's probably my favorite, I'd like to suggest keeping the commas as optional ignored syntax in a branch-terminal position that is stripped by the formatter

view this post on Zulip Anthony Bullard (Feb 21 2025 at 14:09):

So that would be:

match e {
    Admin({ email, .. }) | Guest(email) if email.ends_with("gmail.com") -> email
}

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 14:31):

are there any ambiguity problems with a guard and omitted ->?

match a {
    Ok(num) if num > 3 { Big }
    Ok(num) if num == 3 { Small }
    Err(_) Other
}

it's either unintentionally missed else branch or a guard. how would formatter work in this case?

view this post on Zulip Richard Feldman (Feb 21 2025 at 14:33):

the lack of an else means the only correct way to interpret it would be with a guard

view this post on Zulip Richard Feldman (Feb 21 2025 at 14:34):

although I guess if the whole expression were evaluating to {} then technically it could be an if-without-else :laughing:

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 14:38):

the lack of an else means the only correct way to interpret it would be with a guard

yes, but if I type the following:

match a {
    Ok(num) if num > 3 { Big }
    Err(_) Other
}

what was my intention? this

match a {
    Ok(num) -> if num > 3 { Big } else { Small }
    Err(_) -> Other
}

or that?

match a {
    Ok(num) if num > 3 -> { Big }
    Err(_) Other
}

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 14:40):

it's likely the second variant makes more sense... so you can add another Ok arm immideatly after the first Ok and it will look better than nested branching... ok, no concerns then

match a {
    Ok(num) if num > 3 -> Big
    Ok(num) -> Small
    Err(_) -> Other
}

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 14:46):

that's interesting. are there any cases when if/else expr inside of the pattern arm is better for readability than a guard and an additional arm? useless question tho

view this post on Zulip Norbert Hajagos (Feb 21 2025 at 15:09):

I think this is ambiguous without explicit ->s:

b = | a | {
    match a {
        Ok(num) if num > 3 { return Big }
        Err(_) Other
    }
}

Would this result in a non-exhaustive pattern error due to Ok appearing with only a guard clause, or in a "you are not returnig anything if the num > 3 branch isn't taken" error?

Suggested fix for case 1)

b = | a | {
    match a {
        Ok(num) if num > 3  -> { return Big }
        Ok(_) Small
        Err(_) Other
    }
}

Suggested fix for case 2)

b = | a | {
    match a {
        Ok(num) -> if num > 3  { return Big } else { return Small }
        Err(_) Other
    }
}

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 15:17):

actually, there's a problem that can't be solved by the formatter:

    match a {
        Ok(num) if num > 3 { Big }
        _ Other
    }

if it resolves to

    match a {
        Ok(num) if num > 3 -> Big
        _ -> Other
    }

then there are no compiler warnings. however the intention might have been

    match a {
        Ok(num) -> if num > 3 { Big } # ouch, I forgot about the else case!
        _ -> Other
    }

view this post on Zulip Kiryl Dziamura (Feb 21 2025 at 15:23):

Would this result in a non-exhaustive pattern error due to Ok appearing with only a guard clause, or in a "you are not returnig anything if the num > 3 branch isn't taken" error?

that's exactly what I was talking about above. but there's no real problem with non-exhaustive patterns. in any case, your (or mine from above) example has no false positives regarding exhaustiveness.

the real problem is when the formatter thinks that the match arms are exhaustive when it's probably not.

view this post on Zulip Richard Feldman (Feb 21 2025 at 15:50):

it's probably fine to say in this exact example we don't try to fix it with the formatter, considering it will almost certainly come up in practice approximately zero times ever :big_smile:

view this post on Zulip Norbert Hajagos (Feb 21 2025 at 15:56):

My example isn't about the formatter. It's "what's the error?" which depends on how the -> would be inserted. But the lack of -> makes it ambigous.

view this post on Zulip Richard Feldman (Feb 21 2025 at 16:14):

yeah, sorry - I mean we just say that's an error

view this post on Zulip Richard Feldman (Feb 21 2025 at 16:15):

as opposed to something we allow (with a warning) and then just fix with the formatter

view this post on Zulip jan kili (Feb 21 2025 at 17:20):

If we keep using arrows in match, can should we use =>? It will help greppability and balance the symbol overloading from single+triple to double+double.

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:24):

personally it's always annoyed me that Rust has -> for functions and => for match because until I got used to it, I had to remember which was which

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:24):

I don't think I've ever grepped for => in Rust; I'm not sure what value that actually has in practice as opposed to in theory :sweat_smile:

view this post on Zulip jan kili (Feb 21 2025 at 17:25):

-> for functions and => for match

Fair, but Roc has => for functions too - I wonder if that eases it.

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:26):

yeah that's part of the reason I wanted to try not using an arrow at all :sweat_smile:

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:26):

not having to remember which of the two arrows to use

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:27):

with -> over . I think it's easy because C, C++, and PHP all use -> in exactly that way among mainstream languages, and ReasonML uses it among functional languages, and no language I've ever heard of uses => as a substitute for .

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:27):

but yeah, for match branches it's kinda like "well, ML family languages use -> but Rust uses => and Roc has both -> and => in other places, so..."

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:28):

but I do think if we had the parser accept both and had the formatter silently correct it to the other one, that would address that problem

view this post on Zulip jan kili (Feb 21 2025 at 17:29):

Yeah, if keyboard ergonomics or muscle memory are a significant concern.

view this post on Zulip Joshua Warner (Feb 21 2025 at 17:45):

Using -> and => for purity in function types does seem to be driving a surprising number of other syntax decisions :thinking:

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:46):

I agree, but it's such a great fit for that one use case I'd rather have it driving other syntax decisions than change it :big_smile:

view this post on Zulip Joshua Warner (Feb 21 2025 at 17:47):

Oh, I'm not arguing against purity inference. I'm arguing for either: (1) not allowing that syntax to dictate so much syntax elsewhere, or (2) perhaps using something different to indicate purity in types.

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:47):

yeah I think (2) sounds like a mistake

view this post on Zulip Joshua Warner (Feb 21 2025 at 17:48):

"I like my arrows; stop trying to take away my arrows"

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:48):

the way I see it, -> and => is like a 10 out of 10 unbelievably great fit for that use case

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:48):

and the others are like "well, there are a lot of ways we could go with this..."

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:49):

and I think it's fine if the 10 out of 10 one, which comes up all over the place in code bases, is the variable we don't adjust

view this post on Zulip Richard Feldman (Feb 21 2025 at 17:50):

I would rather pick one of the "this could work" options for other syntaxes than change -> and => to something that's worse

view this post on Zulip jan kili (Feb 21 2025 at 17:55):

To be fair, despite my concern of symbol overloading, we're already doing better on that:

Before:

Now:

view this post on Zulip Joshua Warner (Feb 21 2025 at 17:59):

IMO -> as a notion of "transformation" (from the thing on the left to the thing on the right) is a much more important notion than => vs -> for purity

view this post on Zulip Richard Feldman (Feb 21 2025 at 18:01):

if there's a specific syntax that seems better than -> and => for purity in function types, I'm open to being convinced, but I think that would need its own #ideas thread :big_smile:

view this post on Zulip Joshua Warner (Feb 21 2025 at 18:02):

I think I'm arguing more for (1) above, aka "just keep using arrows elsewhere and it'll be fine"

view this post on Zulip Joshua Warner (Feb 21 2025 at 18:02):

i.e. -> in match

view this post on Zulip Joshua Warner (Feb 21 2025 at 18:04):

In a certain sense, it's still "correct" since the act of matching (picking which branch you want) is definitely still pure

view this post on Zulip Joshua Warner (Feb 21 2025 at 22:05):

This does bring up a possible ambiguity between local function dispatch and match branches with if guards.

match foo {
  Ok(a) if a->is_error()->foo()
  _ -> other()
}

Is that parsed as:

  Ok(a) if a -> (is_error()->foo())

Or:

  Ok(a) if (a->is_error()) -> foo()

?

view this post on Zulip Richard Feldman (Feb 21 2025 at 22:09):

yep, great point

view this post on Zulip Richard Feldman (Feb 21 2025 at 22:10):

well the easiest fix is to use => for this like Rust does :big_smile:

view this post on Zulip jan kili (Feb 21 2025 at 22:17):

As an amendment to my previous point about enhanced greppability, what I'm really thinking of is, in a modal editor, doing something like spawning a cursor for each branch by selecting the =>s and then selecting/editing adjacent expressions in a batch.

view this post on Zulip Richard Feldman (Feb 21 2025 at 22:18):

totally, and I do that sort of thing all the time in general - but in Rust I've never done it with => specifically

view this post on Zulip jan kili (Feb 21 2025 at 22:19):

Also, readability/parsability is ten times more important than editability.

view this post on Zulip Richard Feldman (Feb 21 2025 at 22:32):

yeah, even without the ambiguity, Ok(foo) => a->b is nicer to read than Ok(foo) -> a->b

view this post on Zulip Anthony Bullard (Feb 21 2025 at 22:44):

Just let me know if I need to update the parser/formatter again :rofl:

view this post on Zulip Anthony Bullard (Feb 21 2025 at 23:20):

Sam Mohr said:

get_user_email = |Admin({ email, .. }) or Guest(email)| email

Just letting you know this won't work in function args with the || syntax while also keeping | for pattern alternatives

view this post on Zulip Sam Mohr (Feb 21 2025 at 23:23):

Yep

view this post on Zulip Sam Mohr (Feb 21 2025 at 23:23):

I'm not sure how important that feature really is, but it makes or a better option

view this post on Zulip Brendan Hansknecht (Feb 21 2025 at 23:34):

I feel like it is probably better to require putting that on the next line

view this post on Zulip Brendan Hansknecht (Feb 21 2025 at 23:34):

Still wouldn't require a when is

view this post on Zulip Brendan Hansknecht (Feb 21 2025 at 23:34):

Or match

view this post on Zulip Sam Mohr (Feb 21 2025 at 23:35):

Do you mean requiring the alternating pattern to be in a match statement, or maybe an assignment?

view this post on Zulip Sam Mohr (Feb 21 2025 at 23:35):

AKA banning it from being in the arg?

view this post on Zulip Brendan Hansknecht (Feb 21 2025 at 23:38):

Ban this in general.

get_user_email = |Admin({ email, .. }) | Guest(email)| email

Require:

get_user_email = |user| {
    Admin({ email, .. }) | Guest(email) = user
    email
}

view this post on Zulip Brendan Hansknecht (Feb 21 2025 at 23:39):

Like I don't think we need to user or, I think it is fine to just ban using | in function args (or I guess requiring parens to use | in args)

view this post on Zulip Sam Mohr (Feb 21 2025 at 23:39):

I think we move to or, then that becomes an extra piece of indirection that would just be annoying

view this post on Zulip Sam Mohr (Feb 21 2025 at 23:39):

I'm not saying that we should move to or

view this post on Zulip Sam Mohr (Feb 21 2025 at 23:40):

Just that this seems to be a problem specifically with | meaning multiple things

view this post on Zulip Brendan Hansknecht (Feb 21 2025 at 23:40):

Yep

view this post on Zulip Richard Feldman (Feb 22 2025 at 00:17):

I don't have a strong preference either way for or vs | but I do like that or is more self-documenting for a feature that's not super commonly used (although not exactly rare either)

view this post on Zulip Richard Feldman (Feb 22 2025 at 01:46):

I also don't feel super strongly about or being allowed in patterns outside match, but I do like that it's one less rule for people to learn

view this post on Zulip Richard Feldman (Feb 22 2025 at 01:47):

as in, patterns work exactly the same way everywhere except at the top level where you only get identifiers and that's it (at least for now)

view this post on Zulip Richard Feldman (Feb 22 2025 at 01:50):

but both of those do seem to be points in favor of or

view this post on Zulip Joshua Warner (Feb 22 2025 at 01:58):

except at the top level where you only get identifiers

Oh hmm, didn't realize this was true?

view this post on Zulip Joshua Warner (Feb 22 2025 at 01:58):

If it's an infallible destructure, why can't you?

view this post on Zulip Sam Mohr (Feb 22 2025 at 01:58):

The Rust-based Roc compiler allows patterns at the top level

view this post on Zulip Sam Mohr (Feb 22 2025 at 02:00):

Two points against that probably aren't actually blockers:

view this post on Zulip Sam Mohr (Feb 22 2025 at 02:00):

Richard says that he and Folkert implemented them before

view this post on Zulip Joshua Warner (Feb 22 2025 at 02:02):

It makes for really awkward type annotations

Ahhh yeah, agreed on that

view this post on Zulip Joshua Warner (Feb 22 2025 at 02:03):

I guess I wouldn't be sad

view this post on Zulip Richard Feldman (Feb 22 2025 at 02:04):

I just think they should be unsupported in 0.1.0

view this post on Zulip Sam Mohr (Feb 22 2025 at 02:04):

It would be nice to support it

view this post on Zulip Sam Mohr (Feb 22 2025 at 02:04):

That's fine

view this post on Zulip Richard Feldman (Feb 22 2025 at 02:04):

they can always be added later as a nonbreaking change

view this post on Zulip Richard Feldman (Feb 22 2025 at 02:05):

I think the main reason to do it would be to remove a rule to learn

view this post on Zulip Richard Feldman (Feb 22 2025 at 02:05):

not bc I think it would actually see a lot of use

view this post on Zulip Richard Feldman (Feb 22 2025 at 02:05):

if it were easy then sure, why not? But it's not easy, so I say hold off :big_smile:


Last updated: Jun 16 2026 at 16:19 UTC