I don't love the name Result.after and I'm open to suggestions for a better name
@Tommy Graves suggested Result.try
Elm and Rust call it andThen
Haskell and Ocaml call it bind
Scala calls it flatMap
other ideas?
Result.ifOk? Result.onSuccess?
I like the clarity of either Result.onOk or Result.onSucess
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)
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
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.
Perhaps something like andThen or after is more conversational, but it makes me think the Errs will also be passed into that function
bind and flatMap confuse me greatly, but maybe that's a healthy confusion that will teach me something
As for try, I see it similar to andThen or after, but also: Wlaschin says no
:clown:
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.
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.
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?
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.
Alternatively, are monads in this context of .andThen only used as a container for exception/failure handling, which is inherently binary?
Most of Roc's conventions seem to come from either Elm or Rust, so maybe .andThen is natural here.
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?
(and could the word "Ok" apply reasonably well to their "happy path", assuming they have one and only one?)
JanCVanB said:
Alternatively, are monads in this context of
.andThenonly 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.
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.
Yeah you can have monads with more than 2 constructors but they're not as common.
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
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).
Rust uses andThen and orElse for after / afterErr respectively. I like the idea of orElse. So that is an argument for andThen.
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
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.
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
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...
Yep!
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.
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.
We like to keep the number of operators low for readability and beginner friendliness.
Last updated: Jun 16 2026 at 16:19 UTC