Module Relude_Result
let ok: a e. 'a => t('a, 'e);Result.okconstructs anOkresult from the provided value.
let error: a e. 'e => t('a, 'e);Result.errorconstructs anErrorresult from the provided value.Result.error("Not even") == Error("Not even");
let unit: e. t(unit, 'e);Result.unitis anOkvalue that holds().Result.unit == Ok();
let getOk: a e. t('a, 'e) => option('a);Result.getOkconverts aresultto anoption, returningSomewhen the result wasOkandNoneif 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.getErrorconverts aresultto anoption, turning a resultErrorintoSome(constructed with the error payload), or returningNoneif 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.isOKreturnstruewhen the provided result holds anOkvalue and returnsfalseotherwise.
let isError: a e. t('a, 'e) => bool;Result.isErrorreturnstruewhen the provided result holds anErrorvalue and returnsfalseotherwise.
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 theresultcontext.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
actionto 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.getOrElseattempts to get theOkvalue 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.getOrElseLazyattempts to get theOkvalue 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.mergereturns the inner value from theresult. This requires both theOkandErrorchannels 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,foldis 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.flipswaps the values between theOkandErrorconstructors.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))ifxis of the formOK(v). It returnsError(e)ifxis 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
ifstatement, 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)ifxis of the formOK(v). It returnsError(f(e))ifxis 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))ifxis of the formOk(v); it returnsError(g(e))ifxis 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. Ifxis 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. Ifxis 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
fcnis of the formOk(f), functionfis applied tox. IfxisOk(v), the result isOk(f(v)). IfxisError(err), the error is passed onwards.If
fcnis itself of the formError(err)andxisOk(v),Error(err)is passed on.Finally, if both
fcnandxareError(e1)andError(e2), the result isError(e2).Using
apply()properly is somewhat complex. See the example in the Validation tests for more details. (It usesVOkandVError, 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 ofxandyare 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, andzare 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, andware 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, andqare 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.purewraps its argument in theOkconstructor.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-Resultargument and returns aResultvalue. Ifris 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-Resultargument and returns aResultvalue. Ifris 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.flattenturns a nested result (an outer result that carries a separate result in itsOkchannel) 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 twoResultarguments. 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)whenris of the formError(e); otherwise it returnsr(anOkvalue) 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.recoverensures that the returned result isOkby returning the provided result if it's alreadyOk, or by falling back to the default value (which will be wrapped in theOkconstructor) 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 convertsNonetoError(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.fromOptionLazyturns an option into a result by converting aSomevalue from the option to anOkresult, or by calling the provided function to get an error value, which will be wrapped in theErrorconstructor.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)comparesaandbfor equality as follows:If both are of the form
Ok(..)andOk(..),eqBycallsokEq()with their values and returns a boolean depending on whether they are equal or not.If both are of the form
Error(..),eqBycallserrorEq()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.triescalls 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 theOkconstructor. If the function throws, the exception is caught and wrapped in theErrorconstructor.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.triesAsStringhandles potentially-unsafe functions similar totries, but additionally converts the exception into astringusing JavaScript's magic ability to construct astringfrom 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
Resulttype that you wish to use withValidationin 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
Resulttype that you wish to use withValidationin 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;