Stream: beginners

Topic: ✔ Early return with ?? operator


view this post on Zulip hchac (Feb 27 2025 at 15:48):

Hello,

Is something like the following possible?

solve = |so_far, vals, ops|
    first = List.first(vals) ?? return so_far
    ...
    # continue working with first

If I try this currently, I get:

── TRAILING OPERATOR in 7.roc ──────────────────────────────────────────────────

I am partway through parsing an expression, but I got stuck here:

38│          first = List.first(vals) ?? return so_far
                                         ^

TODO provide more context.

Currently using the following:

        first = if List.len(vals) == 0 then
            return so_far
        else
            List.first(vals) ?? crash "expected non-empty list"

        ...
        # continue using first

But was wondering if it could be shortened to the previously mentioned expression.

view this post on Zulip Anthony Bullard (Feb 27 2025 at 15:56):

I don’t think so, return is not an expression

view this post on Zulip Anthony Bullard (Feb 27 2025 at 15:56):

It is a keyword and more similar to a statement

view this post on Zulip Anthony Bullard (Feb 27 2025 at 15:57):

But i know we’ve talked about else-less ifs when the if has a return is after then

view this post on Zulip hchac (Feb 27 2025 at 16:04):

I see, was hoping the compiler could work some magic in the event of early-return even if an expression is expected since it would clean up my currently working attempt (List.len in an if followed by List.first in one of the branches).

Maybe there's some other cleaner away I could do the same without additional nesting. Basically just want to grab the first element of the list and continue working, otherwise if it's empty return what exists so far.

I may be still thinking about this too much in the imperative mindset (early returns when conditions aren't what we want).

Thanks.

view this post on Zulip Anthony Bullard (Feb 27 2025 at 16:20):

I would typically do a when..is on List.first here

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

Or not even List.first, just when..is, destructuring the list

view this post on Zulip Anthony Bullard (Feb 27 2025 at 16:24):

when vals is
    [] -> crash “….”
    [first, .. as rest] ->
        # continue using first

view this post on Zulip Anthony Bullard (Feb 27 2025 at 16:24):

@hchac ^

view this post on Zulip Anthony Bullard (Feb 27 2025 at 16:28):

Oh! You can also just do

first = List.first(vals)?
# Continue using first

Which is what you want - no need to check length and no block or indentation

view this post on Zulip hchac (Feb 27 2025 at 16:32):

Anthony Bullard said:

Oh! You can also just do

first = List.first(vals)?
# Continue using first

Which is what you want - no need to check length and no block or indentation

I thought about this one, but didn't want to return a Result value. Was shooting for just the value type, no wrapper.

Thanks for letting me know about the [] and [first, .. as rest] syntax in the when cases though! I might have missed it in the tutorial. Very useful syntax!

view this post on Zulip Brendan Hansknecht (Feb 27 2025 at 18:23):

I think early return with ?? is expected to work

view this post on Zulip Brendan Hansknecht (Feb 27 2025 at 18:23):

At least it was discussed and I believe @Richard Feldman suggested that he wanted it to work

view this post on Zulip Brendan Hansknecht (Feb 27 2025 at 18:25):

I think it makes sense that it should work. Same with throwing a crash there if wanted

view this post on Zulip hchac (Feb 27 2025 at 18:27):

The when pattern that @Anthony Bullard showed me is holding me over for now (works quite well), but early return on ?? would also be nice to reduce too much nesting (more whens inside etc)

view this post on Zulip hchac (Feb 27 2025 at 18:31):

Like if I only want to continue down the function as long as x conditions are met:

first = List.first(vals) ?? return so_far
op = List.first(ops) ?? return first
... perform work with first and op ...

which I assume would look like the following at the moment:

when vals is
    [] -> so_far
    [first, .. as restvals] ->
        when ops is
            [] -> first
            [op, .. as restops] ->
                ... perform work with first and op ...

view this post on Zulip Richard Feldman (Feb 27 2025 at 19:35):

yeah exactly, definitely want that to work!

view this post on Zulip Richard Feldman (Feb 27 2025 at 19:39):

which, incidentally, means that both the return and crash keywords need to be expressions (cc @Sam Mohr @Anthony Bullard)

view this post on Zulip Richard Feldman (Feb 27 2025 at 19:39):

so like foo(crash "blah") and foo(return "blah") should both parse as valid, but then give a canonicalization warning of like "hey foo is never actually going to be called..."

view this post on Zulip Richard Feldman (Feb 27 2025 at 19:40):

but parsing them that way means that ?? crash "blah" and ?? return "blah" work

view this post on Zulip Richard Feldman (Feb 27 2025 at 19:40):

and also that like foo = if a { b } else { return "blah" } can work too

view this post on Zulip Richard Feldman (Feb 27 2025 at 19:41):

and then when they're used as statements, which is equivalent to {} = return "blah" that works because the return expression has the type a (or * as we used to call it), and same with crash expressions

view this post on Zulip Sam Mohr (Feb 27 2025 at 19:44):

The word "unbound" was used by Ayaz, which seems a more accurate descriptor

view this post on Zulip Richard Feldman (Feb 27 2025 at 19:46):

totally

view this post on Zulip hchac (Feb 27 2025 at 19:47):

Not sure what Zig calls it, but I believe they handle this scenario as well:

pub fn main() void {
    const vals = std.ArrayList(i32).init(std.heap.page_allocator);
    std.debug.print("{}", .{solve(123, vals)});
}

fn solve(so_far: i32, vals: std.ArrayList(i32)) i32 {
    const first = if (vals.items.len > 0) vals.items[0] else return so_far;
    return first;
}

Returns: 123

view this post on Zulip Anthony Bullard (Feb 28 2025 at 00:33):

Ok, some more work for me to do in the zig compiler as well

view this post on Zulip Anthony Bullard (Feb 28 2025 at 00:34):

And actually now that I think about it ?? return should work with the way it’s desugared

view this post on Zulip Anthony Bullard (Feb 28 2025 at 00:34):

It looks like the parser is the problem here though

view this post on Zulip Notification Bot (Mar 01 2025 at 13:56):

hchac has marked this topic as resolved.


Last updated: Jul 06 2025 at 12:14 UTC