Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve readability, extendability and composability of result custom transformations (pipe API) #453

Open
luc-freyermuth opened this issue Mar 8, 2023 · 1 comment
Assignees

Comments

@luc-freyermuth
Copy link

luc-freyermuth commented Mar 8, 2023

Hello !

We have started using neverthrow at my company and we are very happy with the lib :) It helps us a lot making our code safer and predictable :)

After using it for quite some time, we realised that we were missing a feature that would alllow us to write our custom operators to transform results with a nice syntax. We could extend the Result class but then we would have to rewrite most of the result functionality to return our extended result. So we wrote some utils functions to transform our results.

For example, we do a lot of error translations, so we wrote this function:

function staticMapError<T, E1 extends string, E2>(r: Result<T, E1>, map: Record<E1, E2>): Record<T, E2> {
    return r.mapErr(e => map[e]);
}

// usage

const result = err('hello' as const);

const translatedErrorsResult = staticMapError(result, { hello: 'bonjour', bye: 'aurevoir' });

console.log(translatedErrorsResult._unsafeUnwrapErr()); // 'bonjour'

That is good enough, but when we have to chain multiple transformers, this becomes unreadable very fast. Example :

nullAsError(staticMapError(result, { hello: 'bonjour', bye: 'aurevoir' }))

We use rxjs a lot in out applications, so by taking example on their .pipe() method on observables, we imagined a similar API for Result.

Here is an example of what this API could look like :

// without the pipe API
nullAsError(
   staticMapError(
     result, 
     { hello: 'bonjour', bye: 'aurevoir' }
    )
)

// with the pipe API
result.pipe(
   nullAsError,
   staticMapError({ hello: 'bonjour', bye: 'aurevoir' })
)

This is much more readable as all operators are on the same level, and you read them in the order in which they are applied. pipe would await operator functions that are simply functions that take a result as an input and return a new result.

A similar API could be defined on ResultAsync.

I kept the examples simple here, so we could imagine solutions to avoid using my functions here and simply using map or mapErr, but the more complex the operators, the more beneficial the pipe API becomes.

This API would allow users that have specific needs to implement their own operators without relying on the neverthrow contributors to implement them. Also, most of these operators wouldn't make sense for most users, so it would be a bad idea to add them on the result class.

I would love to get your thoughts about this ! I could provide a PR that implements that, but I would maybe need a bit of help for writing some complex types :)

Thanks !

@luc-freyermuth luc-freyermuth changed the title Improve extendability and composability of result transformations Improve readability, extendability and composability of result custom transformations Mar 8, 2023
@luc-freyermuth luc-freyermuth changed the title Improve readability, extendability and composability of result custom transformations Improve readability, extendability and composability of result custom transformations (pipe API) Mar 8, 2023
@m-shaka m-shaka self-assigned this Sep 6, 2024
@macksal
Copy link
Contributor

macksal commented Oct 18, 2024

This seems like a great idea to me. There are a lot of features I've wanted, but the only convenient way to implement them with a nice syntax is by adding methods to the Result/ResultAsync classes. We could almost drop in rxjs's pipe, the only tricky thing is how to handle sync/async. Would we just allow each operator to both take and return any subtype of either Result or ResultAsync?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants