Module Relude_Result

type t('a, 'e) = Pervasives.result('a'e) =
| Ok('a)
| Error('e)
;
let ok: a e. 'a => t('a'e);

Result.ok constructs an Ok result from the provided value.

let error: a e. 'e => t('a'e);

Result.error constructs an Error result from the provided value.

Result.error("Not even") == Error("Not even");
let unit: e. t(unit, 'e);

Result.unit is an Ok value that holds ().

Result.unit == Ok();
let getOk: a e. t('a'e) => option('a);

Result.getOk converts a result to an option, returning Some when the result was Ok and None if the result was an Error.

Result.getOk(Ok(1066)) == Some(1066);
Result.getOk(Error("bad value")) == None;
let toOption: a e. t('a'e) => option('a);

Result.toOption is an alias for getOk.

let getError: a e. t('a'e) => option('e);

Result.getError converts a result to an option, turning a result Error into Some (constructed with the error payload), or returning None if the provided result was Ok.

Result.getError(Ok(1066)) == None;
Result.getError(Error("bad value")) == Some("bad value");
let isOk: a e. t('a'e) => bool;

Result.isOK returns true when the provided result holds an Ok value and returns false otherwise.

let isError: a e. t('a'e) => bool;

Result.isError returns true when the provided result holds an Error value and returns false 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 the result 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 the Ok value out of a result. If the result is an Error, 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 the Ok value out of a result. If the result is an Error, 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 the result. This requires both the Ok and Error 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 the Ok and Error 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) returns Ok(f(x)) if x is of the form OK(v). It returns Error(e) if x is of the form Error(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 see map() become useful when we have several things to validate. (See apply() and map2(), map3(), etc.)

let mapOk: ('a => 'b) => t('a'c) => t('b'c);

mapOk is an alias for map.

let mapError: a e1 e2. ('e1 => 'e2) => t('a'e1) => t('a'e2);

mapError(f, x) returns Ok(v) if x is of the form OK(v). It returns Error(f(e)) if x is of the form Error(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) returns Ok(f(v)) if x is of the form Ok(v); it returns Error(g(e)) if x is of the form Error(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), function f() returns unit. Thus, f() is used only for its side effects. If x is of the form Ok(v), tap() calls f(v). The tap() function returns the argument x.

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 tapOk: a e. ('a => unit) => t('a'e) => t('a'e);

Result.tapOk is an alias for tap.

let tapError: a e. ('e => unit) => t('a'e) => t('a'e);

In tapError(f, x), function f() returns unit. Thus, f() is used only for its side effects. If x is of the form Error(v), tap() calls f(v). The tap() function returns the argument x.

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 form Ok(f), function f is applied to x. If x is Ok(v), the result is Ok(f(v)). If x is Error(err), the error is passed onwards.

If fcn is itself of the form Error(err) and x is Ok(v), Error(err) is passed on.

Finally, if both fcn and x are Error(e1) and Error(e2), the result is Error(e2).

Using apply() properly is somewhat complex. See the example in the Validation tests for more details. (It uses VOk and VError, 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 of x and y are of the form Ok(xv) and Ok(yv), map2() returns Ok(f(xv, yv)). Otherwise, it returns the last value of type Error(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 of x, y, and z are of the form Ok(xv), Ok(yv), and Ok(zv), map3() returns Ok(f(xv, yv, zv)). Otherwise, it returns the last value of type Error(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 of x, y, z, and w are of the form Ok(xv), Ok(yv), Ok(zv), and Ok(wv), map4() returns Ok(f(xv, yv, zv, wv)). Otherwise, it returns the last value of type Error(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 of x, y, z, w, and q are of the form Ok(xv), Ok(yv), Ok(zv), Ok(wv), and Ok(qv), map5() returns Ok(f(xv, yv, zv, wv, qv)). Otherwise, it returns the last value of type Error(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 the Ok 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 a Result value. If r is of the form Ok(val), then bind() returns f(val). Otherwise, it returns r, which will be an Error(err).

Note: bind() is the same as flatMap(), 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 a Result value. If r is of the form Ok(val), then flatMap() returns f(val). Otherwise, it returns r, which will be an Error(err).

Note: flatMap() is the same as bind(), 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 its Ok 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 two Result arguments. If both are Ok(..), the first one is returned. If only one is Ok(..), it is returned. If both are Error(..), 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) returns f(e) when r is of the form Error(e); otherwise it returns r (an Ok 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 is Ok by returning the provided result if it's already Ok, or by falling back to the default value (which will be wrapped in the Ok constructor) if the provided result is an Error.

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 using getOrElse, or you'll want to indicate to the compiler that the error channel is definitely empty by using handleError.

// 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 form Some(v) to Ok(v), and converts None to Error(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 a Some value from the option to an Ok result, or by calling the provided function to get an error value, which will be wrapped in the Error 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) compares a and b for equality as follows:

If both are of the form Ok(..) and Ok(..), eqBy calls okEq() with their values and returns a boolean depending on whether they are equal or not.

If both are of the form Error(..), eqBy calls errorEq() with their values and returns a boolean depending on whether they are equal or not.

In all other cases, eqBy() returns false.

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 a result. If the provided function succeeds, its return value is wrapped in the Ok constructor. If the function throws, the exception is caught and wrapped in the Error 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 to tries, but additionally converts the exception into a string using JavaScript's magic ability to construct a string 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) converts Ok(val) to VOk(val) and Error(err) to VError(err).

let fromValidation: Relude_Validation.t('a'b) => Pervasives.result('a'b);

fromValidation(vResult) converts VOk(val) to Ok(val) and VError(err) to Error(err).

let toValidationNel: t('a'e) => Relude_Validation.t('a, Relude_NonEmpty.List.t('e));

toValidationNel(vResult) converts Ok(val) to VOk(val) and Error(err) to VError([err]), where the list is of type Relude.NonEmpty.List.

You use this function when you have a Result type that you wish to use with Validation 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) converts Ok(val) to VOk(val) and Error(err) to VError([|err|]), where the array is of type Relude.NonEmpty.Array.

You use this function when you have a Result type that you wish to use with Validation 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 Bifunctor: BsBastet.Interface.BIFUNCTOR with type Bifunctor.t('a, 'e) = t('a'e);
let bimap: ('a => 'b) => ('c => 'd) => Bifunctor.t('a'c) => Bifunctor.t('b'd);
include { ... };
let mapLeft: ('a => 'c) => Bifunctor.t('a'b) => Bifunctor.t('c'b);
let mapRight: ('b => 'd) => Bifunctor.t('a'b) => Bifunctor.t('a'd);
let mapError: ('a => 'b) => Bifunctor.t('c'a) => Bifunctor.t('c'b);
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;
include { ... };
module WithError: (E: BsBastet.Interface.TYPE) => { ... };

Because Result is a bi-functor, we need to capture the error type in order to implement many of the single-type-parameter typeclasses. Doing it like this allows us to unlock a bunch of stuff at once using a single module functor.