Stream: ideas

Topic: Result method to map (and lift) Ok and Err values


view this post on Zulip Pei Yang Ching (Feb 20 2024 at 21:55):

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)

view this post on Zulip Brendan Hansknecht (Feb 20 2024 at 22:32):

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.

view this post on Zulip Brendan Hansknecht (Feb 20 2024 at 22:36):

Also, in this specific case, I think a map followed by a withDefault would be clearer cause the error argument is not actually used.

view this post on Zulip Isaac Van Doren (Feb 20 2024 at 23:34):

Instead of introducing a new function I would do this

result
|> Result.map \r -> ...
|> Result.mapErr \r -> ...

view this post on Zulip Isaac Van Doren (Feb 20 2024 at 23:36):

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

view this post on Zulip Pei Yang Ching (Feb 20 2024 at 23:37):

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)

view this post on Zulip Pei Yang Ching (Feb 20 2024 at 23:44):

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

view this post on Zulip Brendan Hansknecht (Feb 21 2024 at 00:21):

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
...

view this post on Zulip Brendan Hansknecht (Feb 21 2024 at 00:24):

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.

view this post on Zulip Brendan Hansknecht (Feb 21 2024 at 00:35):

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.

view this post on Zulip Brendan Hansknecht (Feb 21 2024 at 00:40):

(deleted)

view this post on Zulip Norbert Hajagos (Feb 21 2024 at 09:04):

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"

view this post on Zulip Norbert Hajagos (Feb 21 2024 at 09:23):

@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:

view this post on Zulip Brendan Hansknecht (Feb 21 2024 at 17:13):

Technically not a solution if you want both paths to shed the result and return a final result type.

view this post on Zulip Brendan Hansknecht (Feb 21 2024 at 17:13):

Which is the case here

view this post on Zulip Brendan Hansknecht (Feb 21 2024 at 17:14):

Also, what a guard if doesn't work cleanly though. You have to unwrap in each if branch

view this post on Zulip Norbert Hajagos (Feb 21 2024 at 18:02):

True
Yes, I thought of the guard if-s unrelated to this specific problem, hence the example of x == 0.

view this post on Zulip Pei Yang Ching (Feb 22 2024 at 08:01):

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

view this post on Zulip Pei Yang Ching (Feb 22 2024 at 08:02):

thanks to both of you for the replies!

view this post on Zulip Norbert Hajagos (Feb 22 2024 at 09:03):

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