The “railway oriented programming” solves an important problem of highlighting the positive flow, and handling all the errors in one place. Also it makes code much cleaner in languages without early return (and with early return too).
But we don’t have a builtin way of doing it. There are already posts asking for Either, but the answer was that there’s already the Result type. Ok, it’s not a problem at all, but there’s no any operations on result type, that makes developers every time reinvent it in their own manner, eg:
From jzon:
module ResultX = {
let mapError = (result, fn) =>
switch result {
| Ok(_) as ok => ok
| Error(err) => Error(fn(err))
}
let sequence = (results: array<result<'ok, 'err>>): result<array<'ok>, 'err> => {
results->Array.reduce(Ok([]), (maybeAcc, res) => {
maybeAcc->Result.flatMap(acc => res->Result.map(x => acc->Array.concat([x])))
})
}
}
From reason-graphql:
module Result = {
let bind = (x, f) =>
bind(
x,
fun
| Ok(x') => f(x')
| Error(_) as err => return(err),
);
let mapError = (x, f) =>
map(
x,
fun
| Ok(_) as ok => ok
| Error(err) => Error(f(err)),
);
let map = (x, f) =>
map(
x,
fun
| Ok(x') => Ok(f(x'))
| Error(_) as err => err,
);
let let_ = bind;
};
Or my Either implementation:
module Either = {
type t<'error, 'value> = Left('error) | Right('value)
let map = (self: t<'error, 'value>, fn: 'value => 'newValue): t<'error, 'newValue> => {
switch self {
| Left(error) => Left(error)
| Right(value) => Right(fn(value))
}
}
let mapLeft = (self: t<'error, 'value>, fn: 'error => 'newError): t<'newError, 'value> => {
switch self {
| Left(error) => Left(fn(error))
| Right(value) => Right(value)
}
}
let asyncMap = (self: t<'error, 'value>, fn: 'value => Promise.t<'newValue>): Promise.t<
t<'error, 'newValue>,
> => {
switch self {
| Left(error) => Left(error)->Promise.resolve
| Right(value) => fn(value)->Promise.thenResolve(newValue => Right(newValue))
}
}
let asyncMapLeft = (self: t<'error, 'value>, fn: 'error => Promise.t<'newError>): Promise.t<
t<'newError, 'value>,
> => {
switch self {
| Left(error) => fn(error)->Promise.thenResolve(newError => Left(newError))
| Right(value) => Right(value)->Promise.resolve
}
}
let chain = (self: t<'error, 'value>, fn: 'value => t<'newError, 'newValue>): t<
'newError,
'newValue,
> => {
switch self {
| Left(error) => Left(error)
| Right(value) => fn(value)
}
}
let asyncChain = (
self: t<'error, 'value>,
fn: 'value => Promise.t<t<'newError, 'newValue>>,
): Promise.t<t<'newError, 'newValue>> => {
switch self {
| Left(error) => Left(error)->Promise.resolve
| Right(value) => fn(value)
}
}
}
At first I’d like to know is there such a pain in community.
After this together create a proposal library such as https://github.com/ryyppy/rescript-promise