Stream: ideas

Topic: Extended guard statements


view this post on Zulip Sam Mohr (Dec 11 2024 at 02:37):

There's a good deal of Rust that is designed to flatten out pipelines, and I agree that I'm almost always happier with "flatter" code, a.k.a. code that allows more assignment of variables in preference

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:38):

One such feature is let-else, e.g.

let Some(value) = maybe_value else {
    return "no-answer";
};

Some(value + 123 * 456)

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:40):

It is a way to add if-style guards instead of

maybe_value
    .map(|value| value + 123 * 456)
    .unwrap_or("no-answer")

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:40):

It means that the reader understands the intermediate steps in the program

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:41):

I’d love some sort match or else assignment now that we have early returns

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:44):

I think we could consider extending the if-return feature to allow that!

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:44):

https://github.com/roc-lang/roc/issues/7105

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:45):

Something like

useResult = \res ->
    Ok val = res else
        return Err "whoops"

    val.callFunc().nowThatItsValid()

view this post on Zulip Notification Bot (Dec 11 2024 at 02:46):

8 messages were moved here from #ideas > Tags and dot syntax by Sam Mohr.

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:48):

(deleted)

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:49):

I think you'd allow the following syntax:

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:50):

Made the example less confusing

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:51):

Deleted my comment that became confusing :wink:

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:51):

Thanks

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:53):

We have become braces-less Rust(or Gleam)

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:53):

I'm okay with this

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:53):

The look of gleam with performance close-ish to Rust

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:53):

Not bad

view this post on Zulip Brendan Hansknecht (Dec 11 2024 at 02:54):

and with platforms

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:54):

I just wonder if we will now hit a wall with white space sensitivity over braces

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:54):

Some people HATE WSS

view this post on Zulip Brendan Hansknecht (Dec 11 2024 at 02:54):

It isn't an issue for languages like python, so should be fine

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:55):

Good point

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:55):

I love the concept and principles of ROC so the makeup doesn’t bother me too much

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:55):

As long as it’s easy to learn and consistent

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:56):

I'm not afraid to admit I like a good coat of paint on my Ferrari

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:56):

Breaking the functional contract or the strong locality would be a deal breaker for me

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:56):

I'm glad it doesn't bother you, then I can get my opinion in first!

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:56):

Locality is very important! Functional is a no brainer

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:56):

Locality is huge. Big huge

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:57):

My least favorite thing in a language is non-locality in imports

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:57):

In imports?

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:57):

Yes

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:58):

Sorry, can you explain what you mean by that?

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:58):

Like open in F#/OCaml

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:58):

Oh yeah, gross

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:58):

Or #include in C/*++

view this post on Zulip Sam Mohr (Dec 11 2024 at 02:58):

(sorry Ocaml fans!)

view this post on Zulip Anthony Bullard (Dec 11 2024 at 02:59):

Even Elixir has this issue with use

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:00):

It’s actually a concern I have with static dispatch

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:00):

When there aren’t type annotations

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:00):

It’s actually the biggest one

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:00):

I’m all for parents-and-commas

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:00):

I'm thinking there may be value in automatically giving type annotations to all top-level defs

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:00):

I think someone is working on that

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:01):

Via formatter

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:01):

Well yes, they're adding a tool to do it, but I want to mandate it

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:01):

I was gonna do it, but someone started a branch without assigning themselves

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:01):

Because without top-level defs having written type annotations, static dispatch could get really confusing

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:01):

Ah, so you can write with none, but to build you have to format it first and get the annotations?

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:02):

And I’m assuming can run/test without?

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:03):

Something like that

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:03):

I’d like to see no-static dispatch Roc with parens and commas

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:04):

It would basically be the language I made, but no function binding sugar and better lambda syntax

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:04):

I'm not sure how to deal with the formatter auto-type-annotating defs while you're writing them

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:04):

Hey, if you're looking for work to do, I don't think anyone is currently working on the parens issue

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:04):

If there even is a GitHub issue

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:04):

I haven’t seen it

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:05):

I have one bug I was gonna look at as soon as I finish my last day of AOC

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:05):

Which is today :joy:

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:05):

Rest in peace, advent of code

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:05):

Christmas will never come :salute:

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:06):

Too much time for someone with a job and a family and who likes to sleep

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:06):

And contribute to Roc of course

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:07):

Though I quite enjoy writing Roc over Rust

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:07):

Your family will understand that you have higher priorities

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:08):

But I wonder what @Richard Feldman would think about doing static dispatch in two stages:

  1. Parens and Commas
  2. Actual Static Dispatch

view this post on Zulip Anthony Bullard (Dec 11 2024 at 03:11):

Phase 1 lands us at a place syntactically that is close to Gleam with WSS over braces. And if we time it with var and for could be an interesting point to decide if we want to try static dispatch for real (and lower the effort to do so)

view this post on Zulip Sam Mohr (Dec 11 2024 at 03:12):

We talked about initially supporting both parens and spaces, we'd want to allow both, though I'm not sure how that would work

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:51):

I think doing it in two stages makes sense

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:51):

they can coexist because in practice nobody writes foo(bar, baz) today

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:51):

(it could parse as foo (bar, baz) but nobody would miss it if it didn't)

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:53):

Sam Mohr said:

There's a good deal of Rust that is designed to flatten out pipelines, and I agree that I'm almost always happier with "flatter" code, a.k.a. code that allows more assignment of variables in preference
One such feature is let-else, e.g.

let Some(value) = maybe_value else {
    return "no-answer";
};

Some(value + 123 * 456)

I'm open to maybe doing some form of this, but I actually dislike the way Rust does it

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:54):

I think the thing that bothers me about it is that you find out it's a conditional after the fact

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:54):

like "doot doot doo going along assigning a thing to a WHOA else!!!"

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:55):

it might be too weird, but one idea could be:

if Ok foo = bar then
    # use foo
else
    return 42

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:55):

just allow single = inside an if conditional

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:56):

I guess then you're still indenting one level, which is fewer than 2, but still more than 0

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:56):

(if I got my math right)

view this post on Zulip Sam Mohr (Dec 11 2024 at 04:56):

That's the equivalent of if-let, but it still doesn't do the important part IMO, which is say "let's focus on the important case of when bar is Ok"

view this post on Zulip Sam Mohr (Dec 11 2024 at 04:57):

Too bad try is already taken

view this post on Zulip Sam Mohr (Dec 11 2024 at 04:57):

UNLESS

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:57):

that's an interesting one!

view this post on Zulip Sam Mohr (Dec 11 2024 at 04:57):

We deprecate try when ? is ready and repurpose it

view this post on Zulip Sam Mohr (Dec 11 2024 at 04:58):

try Ok x = val else
    return "nope"

view this post on Zulip Richard Feldman (Dec 11 2024 at 04:58):

try Ok foo = bar else
    return 42

# use foo

view this post on Zulip Sam Mohr (Dec 11 2024 at 04:58):

Or you pull from the Ruby book

view this post on Zulip Sam Mohr (Dec 11 2024 at 04:58):

unless Ok foo = bar
    return 42

# use foo

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:05):

of note, this one doesn't need any different syntax, because of ?:

Sam Mohr said:

Something like

useResult = \res ->
    Ok val = res else
        return Err "whoops"

    val.callFunc().nowThatItsValid()

it can be:

useResult = \res ->
    val = res.mapErr("whoops")?

    val.callFunc().nowThatItsValid()

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:06):

which I just realized has the same property of "the conditional part comes at the end" but for some reason it doesn't bother me when it's ?, unlike else :thinking:

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:06):

I don't really know why that's my reaction to it! :big_smile:

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:10):

Familiarity bias, perhaps

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:11):

I think your principle of "earlier info is better info" is great, but ? is an written language thing

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:11):

So we know it needs to follow different rules

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:15):

that makes a lot of sense!

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:17):

I think another thing that bugs me about the Rust style (which doesn't come up with ? either) is when you have like:

if let Ok(foo) = bar {
    // ...
} else {
    // ...
}

I think what bugs me about it is that it's semantically equivalent to using match with 2 branches, one of which is _ =>, except that it's more concise

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:17):

which in turn means that it rewards using _ => instead of exhaustive pattern matches

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:17):

and I dislike feeling torn between wanting conciseness and wanting exhaustive pattern matching

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:18):

I would rather it didn't offer me the conciseness option so I can always choose exhaustive pattern matching without having to debate tradeoffs in my head

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:21):

I agree that you should always prefer proper matches over if-let-else

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:21):

I respect Gleam's lack of if-else for this property

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:22):

I think we should do unless-return or try-else-return, but not if-let given that

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:23):

speaking of punctuation, could do something like this:

Ok foo = bar ??
    return 42

# use foo

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:24):

so it's kind of like with_default except lazily evaluated

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:24):

Are we still planning on doing ??, then?

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:24):

I don't think we have a concrete plan right now, but we certainly could

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:25):

actually I guess if it's with_default then it would just be:

val = result ??
    return 42

# use foo

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:25):

or even one line:

val = result ?? return 42

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:25):

I think it made sense with

try readFile! "path" ? FileReadErr
?? []

Since we were trying to encourage good error handling with terseness

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:26):

I actually would love to keep these operators

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:26):

The one I think is more important is the map_err one

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:27):

Because people don't usually actually map_err in Rust, they just slap anyhow on everything, meaning they don't have a "call stack" of context

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:27):

yeah, I definitely prefer map_err over anyhow :thumbs_up:

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:27):

But since ? is staying as it normally works, maybe we can find a different operator for map_err

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:27):

I'm not sure how well an operator would work in method style though

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:27):

Well, the problem with Roc as it is today is that we have anyhow

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:28):

I guess really it's that an operator wouldn't work well with the ? suffix

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:28):

e.g. foo.bar().map_err(Baz)? works fine

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:29):

but (foo.bar() ?? Baz)? is awkward

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:29):

tryThreeThings! = \{} ->
    try validateFile! "first.txt"
    try validateFile! "second.txt"
    try validateFile! "third.txt"

    Ok {}

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:29):

(well it probably wouldn't be ?? I guess, but the point is that an infix operator in the middle of a chain is weird)

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:29):

Which one failed?

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:29):

You're right that map_err is the solution here

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:29):

But it seems like not many people do that, so long as it type checks

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:30):

yeah I'm not sure if that's fixable though

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:30):

Though I don't have any evidence that a one-char operator would improve it... yep

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:30):

any solution is going to be strictly more work than zero work

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:31):

I can only hope that anonymous error unions make it easy enough

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:31):

map_err should be fine, though we probably also want map_err!

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:32):

I think without a map_err operator, we don't want ?? either for the reason you suggested

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:33):

well with_default comes up pretty often as the last thing you do

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:33):

as I recall several mainstream languages have exactly ?? as that operator

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:33):

except they use it for null instead of Result

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:34):

I'm okay with it, but we've decided for every other pipeline-adjacent operator to use words instead

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:34):

pass_to instead of |> or .>

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:34):

map_err instead of ?

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:35):

yeah, but an operator here seems like it has less downside because with_default is most common at the end of a series of chained calls, plus it makes it work much better with ?? return foo

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:35):

seems a little "uneven", but we have operators elsewhere

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:35):

because if it's a method, then it can't be lazily evaluated

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:35):

.with_default(return foo) doesn't work :big_smile:

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:36):

?? return Foo only works for results, right?

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:36):

whereas ?? can work like && and || are intended to in the future

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:36):

I think that's a little limiting

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:36):

yeah it would be like result ?? return Foo

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:36):

hm, how so?

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:38):

UserStatus : [
    LoggedIn(User),
    Inactive,
    Deleted,
]

checkUserActivity : UserStatus -> Result _ _
checkUserActivity = \userStatus ->
    unless LoggedIn user = userStatus
        return Err NotLoggedIn

    checkStatus(user)

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:38):

Though you're right that we want short-circuiting for defaults!

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:38):

So I like that ?? is short-circuiting like && and || will be

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:39):

If you tell users that they should be converting everything to Results, then we get less-documented code

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:39):

ah I see what you mean, yeah it doesn't help in the situation where you want to early-return based on pattern matching on a non-Result :thumbs_up:

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:39):

Like in our canonicalization code where we use Result<Value, Type> because it's a convenient version of Either

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:39):

right haha

view this post on Zulip Sam Mohr (Dec 11 2024 at 05:40):

Instead of doing Value | Type

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:44):

might be confusing, but we could have ?? work differently when used after a single-pattern match:

UserStatus : [
    LoggedIn(User),
    Inactive,
    Deleted,
]

checkUserActivity : UserStatus -> Result _ _
checkUserActivity = \userStatus ->
    LoggedIn user = userStatus ?? return Err NotLoggedIn

    checkStatus(user)

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:45):

although I don't know what it would mean if you didn't return after the ?? in that :laughing:

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:45):

I guess you'd be expected to provide an alternative user or something

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:45):

seems confusing

view this post on Zulip Richard Feldman (Dec 11 2024 at 05:46):

anyway, I gotta go to bed, but it's an interesting topic!


Last updated: Jun 16 2026 at 16:19 UTC