Module Relude_Result
let ok: a e. 'a => t('a, 'e);
Result.ok
constructs anOk
result from the provided value.
let error: a e. 'e => t('a, 'e);
Result.error
constructs anError
result from the provided value.Result.error("Not even") == Error("Not even");
let unit: e. t(unit, 'e);
Result.unit
is anOk
value that holds()
.Result.unit == Ok();
let getOk: a e. t('a, 'e) => option('a);
Result.getOk
converts aresult
to anoption
, returningSome
when the result wasOk
andNone
if the result was anError
.Result.getOk(Ok(1066)) == Some(1066); Result.getOk(Error("bad value")) == None;
let getError: a e. t('a, 'e) => option('e);
Result.getError
converts aresult
to anoption
, turning a resultError
intoSome
(constructed with the error payload), or returningNone
if the provided result wasOk
.Result.getError(Ok(1066)) == None; Result.getError(Error("bad value")) == Some("bad value");
let isOk: a e. t('a, 'e) => bool;
Result.isOK
returnstrue
when the provided result holds anOk
value and returnsfalse
otherwise.
let isError: a e. t('a, 'e) => bool;
Result.isError
returnstrue
when the provided result holds anError
value and returnsfalse
otherwise.
let fold: a e c. ('e => 'c) => ('a => 'c) => t('a, 'e) => 'c;
Result.fold
"handles" both possible cases in a result. The first function maps the inner error to a new type, and the second function maps the ok value to that same type. This effectively lets you get out of theresult
context.A good example of this is a React reducer, where some operation could either fail or succeed, but either way, you want to convert that into an
action
to update your application state.// imagine this tries to load data from some synchronous store like // localstorage, but it could fail to find or decode the data let nextAction = loadTasks() |> Result.fold( _err => SetDataFailure("Data could not be loaded"), data => UpdateData(data) ); // now, regardless of whether our result was successful, we have an action dispatch(nextAction);
let getOrElse: a e. 'a => t('a, 'e) => 'a;
Result.getOrElse
attempts to get theOk
value out of aresult
. If the result is anError
, the provided default value is returned instead.// imagine a function to calculate an average from a array of floats, but it // safely prevents division by zero by returning a result let safeAvg = (values: array(float)) => switch (Array.length(values)) { | 0 => Error("Cannot calculate average") | len => let total = Array.Float.sum(values); Ok(total /. Int.toFloat(len)) }; Result.getOrElse(0.0, safeAvg([|12.3, 9.6, 4.7, 10.8|])) == 9.35; Result.getOrElse(0.0, safeAvg([||])) == 0.0;
let getOrElseLazy: a e. (unit => 'a) => t('a, 'e) => 'a;
Result.getOrElseLazy
attempts to get theOk
value out of aresult
. If the result is anError
, a default value is returned by calling the provided function instead.This function is similar to
getOrElse
, but in this case the default only gets constructed if it's actually needed. This can be useful if the fallback value is expensive to compute.
let merge: a. t('a, 'a) => 'a;
Result.merge
returns the inner value from theresult
. This requires both theOk
andError
channels to hold the same type of value.This is especially useful in cases where you've "handled" the error half using something like
mapError
, and you now want to get the value out. In many cases, though,fold
is probably the simpler alternative.let userGreeting = decodeUser(data) // imagine this returns a result |> Result.map(user => "Hello " ++ user.firstName ++ "!") |> Result.mapError(_ => "Hello unknown user!") |> Result.merge; // if the decode failed... userGreeting == "Hello unknown user!"; // if it succeeded... userGreeting == "Hello Alice!"
let flip: a e. t('a, 'e) => t('e, 'a);
Result.flip
swaps the values between theOk
andError
constructors.flip(Ok(3)) == Error(3); flip(Error("hello")) == Ok("hello");
let compose: a b c e. t('b => 'c, 'e) => t('a => 'b, 'e) => t('a => 'c, 'e);
let andThen: a b c e. t('a => 'b, 'e) => t('b => 'c, 'e) => t('a => 'c, 'e);
let map: a b e. ('a => 'b) => t('a, 'e) => t('b, 'e);
map(f, x)
returnsOk(f(x))
ifx
is of the formOK(v)
. It returnsError(e)
ifx
is of the formError(e)
.map(x => sqrt(float_of_int(x)), Ok(4)) == Ok(2.0); map(x => sqrt(float_of_int(x)), Error("bad")) == Error("bad");
One place you might use
map()
is in validating input to an “ordinary” function. Consider the (highly artificial) example where you have a function that will cube a number, but you only want to do so if the number is even. Here are the functions:type intResult = Relude.Result.t(int, string); let cube = (x) => {x * x * x}; let testEven = (n): intResult => { (n mod 2 == 0) ? Ok(n) : Error(string_of_int(n) ++ " is not even.") };
We can now make calls like this:
map(cube, testEven(12)) == Ok(1728); map(cube, testEven(5)) == Error("5 is not even.");
This is something we could have done with a simple
if
statement, but we will seemap()
become useful when we have several things to validate. (Seeapply()
andmap2()
,map3()
, etc.)
let mapError: a e1 e2. ('e1 => 'e2) => t('a, 'e1) => t('a, 'e2);
mapError(f, x)
returnsOk(v)
ifx
is of the formOK(v)
. It returnsError(f(e))
ifx
is of the formError(e)
.Result.mapError(x => "Err: " ++ x, Ok(4)) == Ok(4); Result.mapError(x => "Err: " ++ x, Error("bad")) == Error("Err: bad");
let bimap: a b e1 e2. ('a => 'b) => ('e1 => 'e2) => t('a, 'e1) => t('b, 'e2);
bimap(f, g, x)
returnsOk(f(v))
ifx
is of the formOk(v)
; it returnsError(g(e))
ifx
is of the formError(e)
.let cube = x => x * x * x; let label = x => "Err: " ++ x; Result.bimap(cube, label, Ok(12)) == Ok(1728); Result.bimap(cube, label, Error("bad")) == Error("Err: bad");
let tap: a e. ('a => unit) => t('a, 'e) => t('a, 'e);
In
tap(f, x)
, functionf()
returnsunit
. Thus,f()
is used only for its side effects. Ifx
is of the formOk(v)
,tap()
callsf(v)
. Thetap()
function returns the argumentx
.Result.tap(x => Js.log(x), Ok(4)) == Ok(4); // prints 4 Result.tap(x => Js.log(x), Error("bad")) == Error("bad"); // prints nothing
let tapError: a e. ('e => unit) => t('a, 'e) => t('a, 'e);
In
tapError(f, x)
, functionf()
returnsunit
. Thus,f()
is used only for its side effects. Ifx
is of the formError(v)
,tap()
callsf(v)
. Thetap()
function returns the argumentx
.tapError(x => Js.log(x), Ok(4)) == Ok(4); // prints nothing tapError(x => Js.log(x), Error("bad")) == Error("bad"); // prints "bad"
let apply: a b e. t('a => 'b, 'e) => t('a, 'e) => t('b, 'e);
apply(fcn, x)
provides a way of creating a chain of validation functions.If
fcn
is of the formOk(f)
, functionf
is applied tox
. Ifx
isOk(v)
, the result isOk(f(v))
. Ifx
isError(err)
, the error is passed onwards.If
fcn
is itself of the formError(err)
andx
isOk(v)
,Error(err)
is passed on.Finally, if both
fcn
andx
areError(e1)
andError(e2)
, the result isError(e2)
.Using
apply()
properly is somewhat complex. See the example in the Validation tests for more details. (It usesVOk
andVError
, but the logic is identical.)
let map2: a b c x. ('a => 'b => 'c) => t('a, 'x) => t('b, 'x) => t('c, 'x);
map2(f, x, y)
has as its first argument a function that takes two values. If both ofx
andy
are of the formOk(xv)
andOk(yv)
,map2()
returnsOk(f(xv, yv))
. Otherwise, it returns the last value of typeError(e)
that it encounters.Here is another artificial example that concatenates a string and integer, but only if the string is non-empty and the number is odd:
let combine = (str, n) => str ++ " " ++ string_of_int(n); type strResult = Relude.Result.t(string, string); let testStr = (s): strResult => (s == "") ? Error("empty string"): Ok(s); let testOdd = (n): intResult => (n mod 2 == 0) ? Error("not odd") : Ok(n); map2(combine, testStr("cloud"), testOdd(9)) == Ok("cloud 9"); map2(combine, testStr("cloud"), testOdd(10)) == Error("not odd"); map2(combine, testStr(""), testOdd(9)) == Error("empty string"); map2(combine, testStr(""), testOdd(10)) == Error("not odd");
let map3: a b c d x. ('a => 'b => 'c => 'd) => t('a, 'x) => t('b, 'x) => t('c, 'x) => t('d, 'x);
map3(f, x, y, z)
has as its first argument a function that takes three values. If all ofx
,y
, andz
are of the formOk(xv)
,Ok(yv)
, andOk(zv)
,map3()
returnsOk(f(xv, yv, zv))
. Otherwise, it returns the last value of typeError(e)
that it encounters.The following example builds on the example from
map2()
and does not show all the possible combinations.let combine = (str, n1, n2) => str ++ " " ++ string_of_int(n1 * n2); let testLimit = (n): intResult => {(n < 100) ? Ok(n) : Error("too big")}; map3(combine, testStr("cloud"), testOdd(3), testLimit(3)) == Ok("cloud 9"); map3(combine, testStr("cloud"), testOdd(2), testLimit(3)) == Error("not odd"); map3(combine, testStr(""), testOdd(3), testLimit(3)) == Error("empty string"); map3(combine, testStr(""), testOdd(10), testLimit(100)) == Error("too big");
let map4: a b c d e x. ('a => 'b => 'c => 'd => 'e) => t('a, 'x) => t('b, 'x) => t('c, 'x) => t('d, 'x) => t('e, 'x);
map4(f, x, y, z, w)
has as its first argument a function that takes four values. If all ofx
,y
,z
, andw
are of the formOk(xv)
,Ok(yv)
,Ok(zv)
, andOk(wv)
,map4()
returnsOk(f(xv, yv, zv, wv))
. Otherwise, it returns the last value of typeError(e)
that it encounters.This example uses validation functions defined in the example from
map3()
.let combine = (s1, n2, n3, n4) => {s1 ++ " " ++ string_of_int(n2 + n3 + n4)}; let testPositive = (n): intResult => {(n > 0) ? Ok(n) : Error("not positive")}; map4(combine, testStr("car"), testOdd(49), testPositive(2), testLimit(3)) == Ok("car 54"); map4(combine, testStr("car"), testOdd(50), testPositive(2), testLimit(200)) == Error("too big"); map4(combine, testStr(""), testOdd(49), testPositive(-5), testLimit(0)) == Error("not positive"); map4(combine, testStr(""), testOdd(48), testPositive(-9), testLimit(200)) == Error("too big"); // all failures
let map5: a b c d e f x. ('a => 'b => 'c => 'd => 'e => 'f) => t('a, 'x) => t('b, 'x) => t('c, 'x) => t('d, 'x) => t('e, 'x) => t('f, 'x);
map5(f, x, y, z, w, q)
has as its first argument a function that takes five values. If all ofx
,y
,z
,w
, andq
are of the formOk(xv)
,Ok(yv)
,Ok(zv)
,Ok(wv)
, andOk(qv)
,map5()
returnsOk(f(xv, yv, zv, wv, qv))
. Otherwise, it returns the last value of typeError(e)
that it encounters.The following examples do not show all the possible combinations.
let combine = (s1, n2, n3, n4, n5) => {s1 ++ " " ++ string_of_int(n2 + n3 + n4 + n5)}; let testNegative = (n): intResult => {(n < 0) ? Ok(n) : Error("not negative")}; map5(combine, testStr("square"), testOdd(5), testPositive(2), testLimit(3), testNegative(-9)) == Ok("square 1"); map5(combine, testStr("square"), testOdd(2), testPositive(5), testLimit(200), testNegative(-3)) == Error("too big"); map5(combine, testStr(""), testOdd(3), testPositive(5), testLimit(7), testNegative(-9)) == Error("empty string"); map5(combine, testStr(""), testOdd(2), testPositive(-2), testLimit(200), testNegative(42)) == Error("not negative"); // all failures
let pure: a e. 'a => t('a, 'e);
Result.pure
wraps its argument in theOk
constructor.Result.pure(3) == Ok(3);
let bind: a b e. t('a, 'e) => ('a => t('b, 'e)) => t('b, 'e);
In
bind(r, f)
,f()
is a function that takes a non-Result
argument and returns aResult
value. Ifr
is of the formOk(val)
, thenbind()
returnsf(val)
. Otherwise, it returnsr
, which will be anError(err)
.Note:
bind()
is the same asflatMap()
, except with the arguments in reverse order.let safeSqrt = (x): Relude.Result.t(float, string) => { (x >= 0.0) ? Ok(sqrt(x)) : Error("cannot be negative") }; bind(Ok(4.0), safeSqrt) == Ok(2.0); bind(Error("invalid float"), safeSqrt) == Error("invalid float");
let flatMap: a b e. ('a => t('b, 'e)) => t('a, 'e) => t('b, 'e);
In
flatMap(f, r)
,f()
is a function that takes a non-Result
argument and returns aResult
value. Ifr
is of the formOk(val)
, thenflatMap()
returnsf(val)
. Otherwise, it returnsr
, which will be anError(err)
.Note:
flatMap()
is the same asbind()
, except with the arguments in reverse order.let safeSqrt = x => (x >= 0.0) ? Ok(sqrt(x)) : Error("cannot be negative"); flatMap(safeSqrt, Ok(4.0)) == Ok(2.0); flatMap(safeSqrt, Error("invalid float")) == Error("invalid float");
let flatten: a e. t(t('a, 'e), 'e) => t('a, 'e);
Result.flatten
turns a nested result (an outer result that carries a separate result in itsOk
channel) into one less layer of result.Note that this only works if the inner and outer result agree about the type of the
Error
.
let alt: a e. t('a, 'e) => t('a, 'e) => t('a, 'e);
alt(r1, r2)
takes twoResult
arguments. If both areOk(..)
, the first one is returned. If only one isOk(..)
, it is returned. If both areError(..)
, the last one is returned.alt(Ok(2), Ok(3)) == Ok(2); alt(Error("bad"), Ok(3)) == Ok(3); alt(Ok(2), Error("worse")) == Ok(2); alt(Error("bad"), Error("worse")) == Error("worse");
let align: a b e. t('a, 'e) => t('b, 'e) => t(Relude_Ior_Type.t('a, 'b), 'e);
Converts two results into a result where either side or both sides have succeded, or a result that fails if both sides failed.
let alignWith: a b c e. (Relude_Ior_Type.t('a, 'b) => 'c) => t('a, 'e) => t('b, 'e) => t('c, 'e);
Similar to map2, but captures successful values from either or both sides
let catchError: a e1 e2. ('e1 => t('a, 'e2)) => t('a, 'e1) => t('a, 'e2);
catchError(f, r)
returnsf(e)
whenr
is of the formError(e)
; otherwise it returnsr
(anOk
value) unchanged.let labelMessage = s => "Attn: " ++ s; catchError(labelMessage, Ok(2)) == Ok(2); catchError(labelMessage, Error("not even")) == Error("Attn: not even");
let handleError: a e. ('e => 'a) => t('a, 'e) => t('a, Relude_Void.t);
handleError(f, r)
converts errors into successful values, and returns a Result where the error channel is voided, to indicate that the error has been handled.
let mapHandleError: a e b. ('a => 'b) => ('e => 'b) => t('a, 'e) => t('b, Relude_Void.t);
Maps the success channel and handles an error on the error channel to end up with a Result of a new type with a voided error channel.
let recover: a e. 'a => t('a, 'e) => t('a, 'e);
Result.recover
ensures that the returned result isOk
by returning the provided result if it's alreadyOk
, or by falling back to the default value (which will be wrapped in theOk
constructor) if the provided result is anError
.Note that the type returned by this function is still a
result
. In most cases, you'll probably want to get out of the result context by usinggetOrElse
, or you'll want to indicate to the compiler that the error channel is definitely empty by usinghandleError
.// imagine a function to calculate an average from a array of floats, but it // safely prevents division by zero by returning a result let safeAvg = (values: array(float)) => switch (Array.length(values)) { | 0 => Error("Cannot calculate average") | len => let total = Array.Float.sum(values); Ok(total /. Int.toFloat(len)) }; Result.recover(0.0, safeAvg([|12.3, 9.6, 4.7, 10.8|])) == Ok(9.35); Result.recover(0.0, safeAvg([||])) == Ok(0.0);
let fromOption: a e. 'e => option('a) => t('a, 'e);
fromOption(defaultError, opt)
converts a value of the formSome(v)
toOk(v)
, and convertsNone
toError(defaultError)
.Result.fromOption("bad value", Some(3)) == Ok(3); Result.fromOption("bad value", None) == Error("bad value");
let fromOptionLazy: a e. (unit => 'e) => option('a) => t('a, 'e);
Result.fromOptionLazy
turns an option into a result by converting aSome
value from the option to anOk
result, or by calling the provided function to get an error value, which will be wrapped in theError
constructor.Note that unlike
fromOption
, this function won't construct the error value unless it's needed, which is more efficient if the error is expensive to construct.let getError = () => "bad value"; Result.fromOptionLazy(getError, Some(3)) == Ok(3); Result.fromOptionLazy(getError, None) == Error("bad value");
let eqBy: a e. ('e => 'e => bool) => ('a => 'a => bool) => t('a, 'e) => t('a, 'e) => bool;
eqBy(errorEq, okEq, a, b)
comparesa
andb
for equality as follows:If both are of the form
Ok(..)
andOk(..)
,eqBy
callsokEq()
with their values and returns a boolean depending on whether they are equal or not.If both are of the form
Error(..)
,eqBy
callserrorEq()
with their values and returns a boolean depending on whether they are equal or not.In all other cases,
eqBy()
returnsfalse
.let clockEqual = (c1, c2) => c1 mod 12 == c2 mod 12; let strEqual = (c1, c2) => c1 == c2; eqBy(strEqual, clockEqual, Ok(14), Ok(2)) == true; eqBy(strEqual, clockEqual, Ok(14), Ok(3)) == false; eqBy(strEqual, clockEqual, Error("not an integer"), Error("not an integer")) == true; eqBy(strEqual, clockEqual, Error("not an integer"), Error("not positive")) == false; eqBy(strEqual, clockEqual, Ok(14), Error("not positive")) == false; eqBy(strEqual, clockEqual, Error("not an integer"), Ok(2)) == false;
let tries: a. (unit => 'a) => t('a, exn);
Result.tries
calls the provided unsafe function (which may throw an exception), and returns its value safely wrapped in aresult
. If the provided function succeeds, its return value is wrapped in theOk
constructor. If the function throws, the exception is caught and wrapped in theError
constructor.tries(() => int_of_string("37")) == Ok(37); tries(() => int_of_string("four")); // returns an exn
let triesAsString: a. (unit => 'a) => t('a, Js.String.t);
Result.triesAsString
handles potentially-unsafe functions similar totries
, but additionally converts the exception into astring
using JavaScript's magic ability to construct astring
from anything.Result.triesAsString(() => int_of_string("37")) == Ok(37); Result.triesAsString(() => int_of_string("four")) == Error("Failure,-2,int_of_string");
let toValidation: Pervasives.result('a, 'b) => Relude_Validation.t('a, 'b);
toValidation(result)
convertsOk(val)
toVOk(val)
andError(err)
toVError(err)
.
let fromValidation: Relude_Validation.t('a, 'b) => Pervasives.result('a, 'b);
fromValidation(vResult)
convertsVOk(val)
toOk(val)
andVError(err)
toError(err)
.
let toValidationNel: t('a, 'e) => Relude_Validation.t('a, Relude_NonEmpty.List.t('e));
toValidationNel(vResult)
convertsOk(val)
toVOk(val)
andError(err)
toVError([err])
, where the list is of typeRelude.NonEmpty.List
.You use this function when you have a
Result
type that you wish to use withValidation
in order to accumulate a list of errors.toValidationNel(Ok(1066)) == Relude.Validation.VOk(1066); toValidationNel(Error("not odd")) == Relude.Validation.VError( Relude.NonEmpty.List.pure("not odd"));
let toValidationNea: t('a, 'e) => Relude_Validation.t('a, Relude_NonEmpty.Array.t('e));
toValidationNea(vResult)
convertsOk(val)
toVOk(val)
andError(err)
toVError([|err|])
, where the array is of typeRelude.NonEmpty.Array
.You use this function when you have a
Result
type that you wish to use withValidation
in order to accumulate an array of errors.toValidationNea(Ok(1066)) == Relude.Validation.VOk(1066); toValidationNea(Error("not odd")) == Relude.Validation.VError( Relude.NonEmpty.Array.pure("not odd")); toValidationNea(Error("not odd"));
module Bifoldable: BsBastet.Interface.BIFOLDABLE with type Bifoldable.t('a, 'e) = t('a, 'e);
let bifoldLeft: ('a => 'b => 'a) => ('a => 'c => 'a) => 'a => Bifoldable.t('b, 'c) => 'a;
let bifoldRight: ('a => 'b => 'b) => ('c => 'b => 'b) => 'b => Bifoldable.t('a, 'c) => 'b;