Stream: ideas

Topic: Better name for `Result.after`?


view this post on Zulip Richard Feldman (Jul 08 2022 at 03:26):

I don't love the name Result.after and I'm open to suggestions for a better name

view this post on Zulip Richard Feldman (Jul 08 2022 at 03:26):

@Tommy Graves suggested Result.try

view this post on Zulip Richard Feldman (Jul 08 2022 at 03:28):

Elm and Rust call it andThen

view this post on Zulip Richard Feldman (Jul 08 2022 at 03:28):

Haskell and Ocaml call it bind

view this post on Zulip Richard Feldman (Jul 08 2022 at 03:30):

Scala calls it flatMap

view this post on Zulip Richard Feldman (Jul 08 2022 at 03:30):

other ideas?

view this post on Zulip Brendan Hansknecht (Jul 08 2022 at 03:33):

Result.ifOk? Result.onSuccess?

view this post on Zulip jan kili (Jul 08 2022 at 04:57):

I like the clarity of either Result.onOk or Result.onSucess

view this post on Zulip jan kili (Jul 08 2022 at 04:59):

I believe the builtins currently only expose ok/err, so we probably shouldn't confuse the language by adding the seemingly-interchangeable success/failure (except in documentation, to clarify what ok/err mean)

view this post on Zulip jan kili (Jul 08 2022 at 05:02):

I'm only now learning about "railway oriented programming", which I hope is the core concept behind monads because I'm still a confusoid in the category of endofunky regarding monads

view this post on Zulip jan kili (Jul 08 2022 at 05:03):

Coming from scripting languages with things like event handlers, Result.onOk tells me that this is a function that will be run if the previous step in the pipeline returned an Ok... which I think is the core concept to communicate here.

view this post on Zulip jan kili (Jul 08 2022 at 05:04):

Perhaps something like andThen or after is more conversational, but it makes me think the Errs will also be passed into that function

view this post on Zulip jan kili (Jul 08 2022 at 05:04):

bind and flatMap confuse me greatly, but maybe that's a healthy confusion that will teach me something

view this post on Zulip jan kili (Jul 08 2022 at 05:07):

As for try, I see it similar to andThen or after, but also: Wlaschin says no

view this post on Zulip jan kili (Jul 08 2022 at 05:08):

:clown:

view this post on Zulip Brian Carroll (Jul 08 2022 at 07:05):

Yeah that's the core concept! I like Wlaschin's railway analogy because it focuses on what monads are useful for. Just a handy "pattern" when you're working with container types.
Elm uses the same function name, andThen, in most of the different monad packages. That helps you to eventually see the pattern in your own time. Task.andThen, Result.andThen, Maybe.andThen... but then there's an exception for List.concatMap because it's more descriptive. To me Result.andThen makes total sense as a name and the rest... sorta?
Looks like Scala took the name that makes sense for List and then made everything else use that.
So I think this is a consideration. Do we want to make this larger pattern obvious so that people can learn to use it? Or give each package a custom name that makes sense for it.

view this post on Zulip Brian Carroll (Jul 08 2022 at 07:10):

I like onOk because you can make it symmetric with onError. Often Ok gets some name that fits the pattern and Err is an afterthought. But they work exactly the same so I think the names should show that. Not sure what the counterpart of try would be in that case.

view this post on Zulip jan kili (Jul 08 2022 at 07:14):

Interesting - I hadn't considered how a name like Result.after might better align the Result type with other builtin monads like Task etc. I assume that .onOk might not work for other monds because Ok is a Result-specific concept?

view this post on Zulip jan kili (Jul 08 2022 at 07:25):

One thing that concerns me about the "make this larger pattern obvious" approach is that it might project assumptions / extrapolate biases. For example, Result has a binary data model (Ok & Err, success & failure, only two railway tracks), but might there be non-binary monads? For a monad with ternary state, I sure would like to have three transformation wrappers like, for example: .onGreen, .onYellow, and .onRed. However, again, I'm new to monads and don't know any monad-friendly languages Elm or Haskell or Scala, so maybe that's a solved problem for .bind and .andThen.

view this post on Zulip jan kili (Jul 08 2022 at 07:29):

Alternatively, are monads in this context of .andThen only used as a container for exception/failure handling, which is inherently binary?

view this post on Zulip jan kili (Jul 08 2022 at 07:32):

Most of Roc's conventions seem to come from either Elm or Rust, so maybe .andThen is natural here.

view this post on Zulip jan kili (Jul 08 2022 at 07:38):

Maybe I'm getting ahead of myself - what are some other types/packages/situations that this name might be able to apply to, if we took the "make this larger pattern obvious" approach?

view this post on Zulip jan kili (Jul 08 2022 at 07:40):

(and could the word "Ok" apply reasonably well to their "happy path", assuming they have one and only one?)

view this post on Zulip Brian Carroll (Jul 08 2022 at 07:47):

JanCVanB said:

Alternatively, are monads in this context of .andThen only used as a container for exception/failure handling, which is inherently binary?

This is an interesting question. Are the Elm monads that use the name andThen all "failure handling" monads and... well yeah, they might be actually! I hadn't noticed that.

view this post on Zulip Brian Carroll (Jul 08 2022 at 07:50):

Personally I am happy with either "use the same name for all monads" or "give each one a name that makes sense for it" but I'm a bit wary of making new categories beyond that. I suspect those categories will just break down at some point.

view this post on Zulip Brian Carroll (Jul 08 2022 at 07:50):

Yeah you can have monads with more than 2 constructors but they're not as common.

view this post on Zulip Qqwy / Marten (Jul 08 2022 at 08:04):

It's an interesting question. Result.andThen reads relatively nicely when building a pipeline. (This is what is done in e.g. Elm):

parseUser input
|> Result.andThen validateUser
|> Result.andThen saveUser
|> Result.andThen sendConfirmationEmail

But it feels a bit weird, 'backwards', when backpassing. (But maybe that just takes some getting used to)

unvalidatedUser <- Result.andThen parseUser input
validatedUser <- Result.andThen validateUser unvalidatedUser
savedUser <- Result.andThen saveUser validatedUser
sendConfirmationEmail

view this post on Zulip Qqwy / Marten (Jul 08 2022 at 08:10):

Currently we use Effect.after, Task.await, List.joinMap for the various monadic binds.
Those seem more natural than picking one name for all of them (regardless of what that name might be).

view this post on Zulip Qqwy / Marten (Jul 08 2022 at 08:12):

Rust uses andThen and orElse for after / afterErr respectively. I like the idea of orElse. So that is an argument for andThen.

view this post on Zulip Qqwy / Marten (Jul 08 2022 at 08:42):

Another possible name might be Result.withOk / Result.withErr. Here, 'with' is used to indicate that we're going to run some more code with the outcome.
(Side note: we also have Result.withDefault that does not return a result, but I don't think that is necessarily bad for this proposal.)

Nonetheless, I think this reads very naturally in backpassing style.

unvalidatedUser <- Result.withOk parseUser
savedUser <- Result.withOk saveUser
sendConfirmationEmail

view this post on Zulip Brendan Hansknecht (Jul 08 2022 at 14:24):

andThen read nice but it doesn't sound like it is only for the success case. So I prefer onOk or onSuccess.
To me, withOk sounds like it is a function that takes a value and wraps it in an Ok and then passes it to a function. Kinda like how withFile would maybe take a path, open a file, and then pass the file into a function.

view this post on Zulip jan kili (Jul 08 2022 at 15:17):

I assume the above example is representative and unbiased toward any word choice, so here's how onOk looks:

parseUser input
|> Result.onOk validateUser
|> Result.onOk saveUser
|> Result.onOk sendConfirmationEmail

unvalidatedUser <- Result.onOk parseUser input
validatedUser <- Result.onOk validateUser unvalidatedUser
savedUser <- Result.onOk saveUser validatedUser
sendConfirmationEmail

view this post on Zulip jan kili (Jul 08 2022 at 15:23):

Would an onErr addendum to the pipeline be something like |> Result.onErr addDebugInfo foo bar? I don't think I've ever transformed the failure case of a monad...

view this post on Zulip Qqwy / Marten (Jul 08 2022 at 15:37):

Yep!

view this post on Zulip Qqwy / Marten (Jul 08 2022 at 15:37):

And the nice thing about Roc's result type is that the different error possibilities are aggregated and we're forced to handle them all in, in this case, addDebugInfo.

view this post on Zulip Krzysztof Skowronek (Jul 11 2022 at 07:04):

i'm new here, but what is Roc view on operators? In F# there is a standard symbol for bind >=>, could be <=< for backapssing. It's far less readable than a name (but if it's standard, it could get traction), but I really doubt that there is a word that would work nicely when read both left-to-right and right-to-left.

view this post on Zulip Anton (Jul 11 2022 at 09:39):

We like to keep the number of operators low for readability and beginner friendliness.


Last updated: Jun 16 2026 at 16:19 UTC