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.
I don’t think so, return is not an expression
It is a keyword and more similar to a statement
But i know we’ve talked about else-less ifs when the if has a return is after then
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.
I would typically do a when..is on List.first here
Or not even List.first, just when..is, destructuring the list
when vals is
[] -> crash “….”
[first, .. as rest] ->
# continue using first
@hchac ^
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
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!
I think early return with ??
is expected to work
At least it was discussed and I believe @Richard Feldman suggested that he wanted it to work
I think it makes sense that it should work. Same with throwing a crash there if wanted
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)
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 ...
yeah exactly, definitely want that to work!
which, incidentally, means that both the return
and crash
keywords need to be expressions (cc @Sam Mohr @Anthony Bullard)
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..."
but parsing them that way means that ?? crash "blah"
and ?? return "blah"
work
and also that like foo = if a { b } else { return "blah" }
can work too
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
The word "unbound" was used by Ayaz, which seems a more accurate descriptor
totally
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
Ok, some more work for me to do in the zig compiler as well
And actually now that I think about it ?? return should work with the way it’s desugared
It looks like the parser is the problem here though
hchac has marked this topic as resolved.
Last updated: Jul 06 2025 at 12:14 UTC