neverthrow: Why can't I return different error types in a "andThen" chain?
Hi,
Thank you for publishing this!
I wanted to get started right away but ran into a seemingly trivial problem I am too much of a noob to solve. Any help would be appreciated!
The compiler complains when I want to compose multiple results with the .andThen() method
type E1 = 'invalid'
type E2 = 'also invalid'
type E3 = 'invalid again'
type Errors = E1 | E2 | E3
const f1 = (sth: unknown) => Result<string, E1>
const f2 = (input: string) => Result<string, E2>
const f3 = (thing: string) => Result<string, E3>
const result: Result<string, Errors> = f1('test')
.andThen(f2)
.andThen(f3)
The compiler complains that that the result of f1 is not assignable to the result of f2, specifically Result<string, E1> is not assignable to Result<string, E2>.
I thought that the mismatch of results and function parameters wouldn’t hurt, because .andThen() doesn’t even call the next function if the result is of type Err. Any idea how I could help with the type inference?
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Comments: 20 (16 by maintainers)
Alright folks … After 8 months of thinking through this, I think I am finally persuaded to go down the route of joining types.
I’m planning on doing some work soonish on this. Maybe I’ll get around to it this week.
I realized that because Rust and Haskell and basically all the other well-known languages that have a
Resultdon’t have subtyping. So it’s literally impossible to doT | A… it’s just not part of those languages. But in typescript, this is allowed!Hi there, I’ve been following this discussion for quite a while now from the sidelines and I do understand @supermacro 's reservations (to some extend)
This is definetely an idioamtic approach in most circumstances, and you are right that
andThenorbindor however the equivalent is called in other languages doesn’t allow a different error type to be returned (AFAIK).BUT this approach comes with a cost (and is also sometimes criticized in the respective communities): composability on the macro level can only be achieved by sacrifizing type expressiveness at the mirco level. @supermacro has demonstrated this in one of his previous examples pretty well. Compare: (modified from the OP)
with the proposed solution:
I left out the dicrimininator but I think my argument still stands: In order to compose arbitrary functions returning
Resulttypes, one has to alter or overload their signatures. Thus reducing the expressiveness of the signature.Additionally it is not possible to centrally handle
Resulttypes at the moment, because the error types of a given pipeline never line up.I was drawn to this GREAT library, because it allows me to be expressive with error paths. At the same time, the possibility to easily compose different functions without them having to know the context in which they are composed would be a really BIG WIN. Especially, since the propsed changes by @paduc are minimal and non-breaking, I would be in favour of changing the interface.
I hope you don’t mind me jumping in this discussion.
I see how having a union Error type can solve the issue but I’m not sure it’s the only solution.
We could also have
andThenreturn an error of type of the union of possible errors in the chain.Example:
The only change necessary would be to change the signature of
andThentoThe implementation itself doesn’t need to change and the change would be non-breaking because
E | E === E, meaning that if you have code that chained multipleResultwith the same error type, the union would be of that error type. It wouldn’t interfere with the short-circuiting of the function inErr.I see this capability as interesting when working with an external module that returns a
Result<T, ModuleSpecificError>. It’s error type is not necessarily compatible with my local error type (maybe the error is in amessageproperty while my local errors use anerrproperty).It’s also a nice way of having the compiler tell me exactly which types of errors can occur in my chain.
Alrighty, I have published a new version behind a
betatag for now.Release notes:
https://github.com/supermacro/neverthrow/releases/tag/v4.1.0-beta.0
I am closing this issue, but feel free to open a new issue regarding this topic!
Hurray ! Tell me if I can be of any help !
Hi @supermacro !
I would definitely welcome a cleaner alternative to my “hack” (it’s a one-time thing though).
A cli-tool seems a fair bit of work. I have seen npm packages that provide “alternate” modes for their libs and maybe
neverthrowcould do the same.Something like:
I’m still hoping this could be the default behaviour btw 😉