I'd like to see a function like rust's Result::map_or_else where both Ok and Err are transformed.
It would be really nice for guard-styled error handling, like if error then return foo, there are existing functions that could do it but having a function that can convert into non-Result type at the same time has its uses.
an example impl and usage:
catch : Result ok err, (err -> out), (ok -> out) -> out
catch = \result, onErr, onOk ->
when result is
Ok ok -> onOk ok
Err err -> onErr err
foo =
res <- Task.await (readFile path)
content <- res |> catch \_ -> defaultConfig
# more operations here...
parseConfig content
(I borrowed the name catch from zig)
I don't think it would generally work well with backpassing. I think the extra lambda definitely will lead to some confusion.
But generally idea definitely makes sense.
I am not a fan of catch as a name. What exactly is it "catching"? It is mapping both the ok and error case.
Also, in this specific case, I think a map followed by a withDefault would be clearer cause the error argument is not actually used.
Instead of introducing a new function I would do this
result
|> Result.map \r -> ...
|> Result.mapErr \r -> ...
One advantage of doing it like this is that you can use it in a pipeline and don't need to put parens around the first lambda like you would otherwise
when I wrote down the function it looked eerily similar to zig's catch, so that's what I picked,
// zig code
foo = bar catch |err| {return baz(err);};
I'm not sure what it should be named honestly
I originally wanted a way to unwrap the result and transforming the value at the same time, but I feel like this could be a great way to write code in the style of if error then return foo;
in terms of syntax, I think it's great. Again, it's reminiscent of procedural style code of if error { return foo;} that I'm more used to.
in terms of how easy it is to understand, I think it's in the same category as Result.try: I have no idea what it actually is unless I stop and think about it but I know when to use it (mostly cuz of repeating so much)
Isaac Van Doren said:
Instead of introducing a new function I would do this
result |> Result.map \r -> ... |> Result.mapErr \r -> ...
in this case a simple when... is would probably suffice but for a longer function with multiple errors to handle it would start to nest too much
One related feature I have always wanted to try is if with implicit else. This would be for early returns.
if ... then
SomethingToReturn
# implicit else with no indent for rest of code
...
Also, the reason I don't like catch is that it makes errors sound exceptional and like a panic/throw with catch. It is not. Just a value.
Oh, though I guess early return doesn't help here exactly. It needs to be an early return that unwraps the result. A standard if Result.isErr res then .... would not unwrap the result in either branch.
(deleted)
Yes, I have thought about guard if-s as well with no indentation on the else block. Sometimes the indentation for roc feels too mutch for how little the my program is. But on the other hand, it is very readable, so I don't mind. I think it would be confusing for if to work with or without an else. For such endeavours, I like this syntax more (I know, discussing syntax is the most important for a lang :sweat_smile: )
if x == 0 return
SomethingToReturn
# implicit else branch without indentation
But to be honest, I would rather have more indentation than a meeting with my coworkers about "what style of if should we use"
@Pei Yang Ching I think this will be solved with the ! operator. Not in the style that you are used to, but completely differently. it wouldn't have guard style (i fully understand why you like those). Quite a long read, but you can find the discussion here. I suggest reading the updated proposal. There was something with if statemets that looked like early returns, but I think it was for returning Task.ok {}. It essentially makes it so that you can have a ! at the end of a result producing function to make it automatcally use Result.try on the return value for the code that follows it, without indentation (works the same way with tasks as well). That way you would write code focusing on the happy path and at the end, you could handle the errors that occured at once, since errors accumulate in Roc. I like that it is very different from what I am used to. Though, you can see that I haven't given up on early returns from my prev comment :smile:
Technically not a solution if you want both paths to shed the result and return a final result type.
Which is the case here
Also, what a guard if doesn't work cleanly though. You have to unwrap in each if branch
True
Yes, I thought of the guard if-s unrelated to this specific problem, hence the example of x == 0.
reading through the ! thread I think something like this would work:
res <- readFile fileName |> Result.mapErr! \err -> doSomething err
still might be a good idea to add a function specifically to unwrap and handle both paths, since it's a bit of a mental gymnastic to come up with this, but it's better to hold off till ! is merged and see again if what I suggested is needed
thanks to both of you for the replies!
Thanks for contributing ideas!
We will see what the fate of backpassing will be after we have!-s. I think your example would have = in place of <- (with the functionality being the same). I get what you are saying. I think there would be ppl who would like to use such style.
Last updated: Jun 16 2026 at 16:19 UTC