Module Relude_Validation
type t('a, 'e)=
;|VOk('a)|VError('e)Similar to result, but has an Applicative instance that collects the errors using a semigroup, rather than fail-fast semantics.
let ok: a e. 'a => t('a, 'e);ok()wraps a value inVOk
let error: a e. 'e => t('a, 'e);error(val)wraps the value in aVError().error("Not even") == VError("Not even");
let errorNel: a e. 'e => t('a, Relude_NonEmpty.List.t('e));Puts an error into a NonEmptyList on the error side of the validation
let errorNea: a e. 'e => t('a, Relude_NonEmpty.Array.t('e));Puts an error into a NonEmptyArray on the error side of the validation
let isOk: a e. t('a, 'e) => bool;isOk(v)returnstrueifvis of the formVOk(val);falseotherwise.
let isError: a e. t('a, 'e) => bool;isError(x)returnstrueifxis of the formVError(val);falseotherwise.
let map: a b e. ('a => 'b) => t('a, 'e) => t('b, 'e);map(f, x)returnsVOk(f(x))ifxis of the formVOk(v). It returnsVError(e)ifxis of the formVError(e).map((x) => {sqrt(float_of_int(x))}, VOk(4)) == VOk(2.0); map((x) => {sqrt(float_of_int(x))}, VError("bad")) == VError("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 formVOk(v),tap()callsf(v). Thetap()function returns the argumentx.tap(x => Js.log(x), VOk(4)) == VOk(4); // prints 4 tap(x => Js.log(x), VError("bad")) == VError("bad"); // prints nothing
let mapError: a e1 e2. ('e1 => 'e2) => t('a, 'e1) => t('a, 'e2);mapError(f, x)returnsVOk(v)ifxis of the formVOk(v). It returnsVError(f(e))ifxis of the formVError(e).mapError(x => "Error: " ++ x, VOk(4)) == VOk(4); mapError(x => "Error: " ++ x, VError("bad")) == VError("Error: bad");
let mapErrorsNea: a e1 e2. ('e1 => 'e2) => t('a, Relude_NonEmpty.Array.t('e1)) => t('a, Relude_NonEmpty.Array.t('e2));mapErrorsAsNea()applies a function to each error in aNonEmpty.Arrayof errors in the error channel of theValidation.
let mapErrorsNel: a e1 e2. ('e1 => 'e2) => t('a, Relude_NonEmpty.List.t('e1)) => t('a, Relude_NonEmpty.List.t('e2));mapErrorsAsNel()applies a function to each error in aNonEmpty.Listof errors in the error channel of theValidation.
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 formVError(v),tap()callsf(v). Thetap()function returns the argumentx.tapError(x => Js.log(x), VOk(4)) == VOk(4); // prints nothing tapError(x => Js.log(x), VError("bad")) == VError("bad"); // prints "bad"
let bimap: a b e1 e2. ('a => 'b) => ('e1 => 'e2) => t('a, 'e1) => t('b, 'e2);bimap(f, g, x)returnsVOk(f(v))ifxis of the formVOk(v); it returnsVError(g(e))ifxis of the formVError(e).let cube = x => x * x * x; let label = x => "Error: " ++ x; bimap(cube, label, VOk(12)) == VOk(1728); bimap(cube, label, VError("bad")) == VError("Error: bad");
let bitap: a e. ('a => unit) => ('e => unit) => t('a, 'e) => t('a, 'e);bitap(f, g, x)the functionsf()andg()both returnunit, so they are used only for their side effects. Ifxis of the formVOk(v),bitap()callsf(v); ifxis of the formVError(e),bitap()callsg(e). In either case,bitap()returnsx.let printCube = x => Js.log(x * x * x); let printLabel = x => Js.log("Error: " ++ x); bitap(printCube, printLabel, VOk(12)) == VOk(12); // prints 1728 bitap(printCube, printLabel, VError("bad")) == VError("bad"); // prints "Error: bad"
let applyWithAppendErrors: a b e. ('e => 'e => 'e) => t('a => 'b, 'e) => t('a, 'e) => t('b, 'e);apply(valFcn, x, appErrFcn)provides a way of creating a chain of validation functions, accumulating errors along the way.If
valFcnis of the formVOk(f), functionfis applied tox. IfxisVOk(v), the result isVOk(f(v)). IfxisVError(err), the error is passed onwards.If
valFcnis itself of the formVError(err)andxisVOk(v),VError(err)is passed on.Finally, if both
valFcnandxareVError(e1)andVError(e2), the result isVError(appendErrFcn(e1, e2)).Using
apply()properly is somewhat complex. See the example in the__tests__/Relude_Validation_test.refile for more details.
let alignWithAppendErrors: a b e. ('e => 'e => 'e) => t('a, 'e) => t('b, 'e) => t(Relude_Ior_Type.t('a, 'b), 'e);let alignWithWithAppendErrors: a b c e. ('e => 'e => 'e) => (Relude_Ior_Type.t('a, 'b) => 'c) => t('a, 'e) => t('b, 'e) => t('c, 'e);let pure: a e. 'a => t('a, 'e);pure(val)wraps its argument in aVOk().pure(3) == VOk(3);
let bind: a b e. t('a, 'e) => ('a => t('b, 'e)) => t('b, 'e);flatMapV(x, f)returnsf(v)whenxis of the formVOk(v), and returnsxunchanged when it is of the formVError(v).Note:
Validationis not a traditional monad in that it's purpose is to collect errors during applicative validation. UsingflatMapwill cause all previous errors to be discarded.let mustBeEven = x => (x mod 2 == 0) ? VOk(x) : VError("not even"); flatMapV(VOk(12), mustBeEven) == VOk(12); flatMapV(VOk(3), mustBeEven) == VError("not even"); flatMapV(VError("not an int"), mustBeEven) == VError("not an int");
let flatMap: a b e. ('a => t('b, 'e)) => t('a, 'e) => t('b, 'e);bindis the same asflatMapwith the arguments flipped.
let fromResult: Pervasives.result('a, 'b) => t('a, 'b);fromResultconverts a value of typeresultto aValidation.t, returningVOk(x)if the result wasOk(x)andVError(x)if the result wasError(x).
let toResult: t('a, 'b) => Pervasives.result('a, 'b);toResultconverts a value of typeValidation.ttoresult.
let fromOption: a e. 'e => option('a) => t('a, 'e);fromOptionconverts anoptioninto aValidation.t, returningVOk(x)if the option wasSome(x)andVError(constructed with the provided error) if the option wasNone.
let fromOptionLazy: a e. (unit => 'e) => option('a) => t('a, 'e);fromOptionLazyconverts anoptioninto aValidation.t, similar tofromOption, except that provided error is constructed lazily by calling a function. This is useful if the error is expensive to construct, especially since it may not be needed.
let fold: a e c. ('e => 'c) => ('a => 'c) => t('a, 'e) => 'c;fold(errFn, okFn, x)returnsokFn(v)when the provided Validation (x) isVOk(v); it returnserrFn(e)when the Validation isVError(e). This is effectively a function that allows you to "handle" both possible states of the Validation.let positiveInt = str => Validate.( Int.fromtString(str) |> fromOption(`InvalidInput) |> flatMapV(x => x < 0 ? error(`NotPositive) : pure(x)) ); let showError = fun | `InvalidInput => "The provided string was not an int" | `NotPositive => "The provided int was negative"; let errMsg = x => "Something went wrong: " ++ showError(x); let succMsg = x => "Found valid positive int: " ++ Int.toString(x); // "Something went wrong: The provided string was not an int" fold(errMsg, succMsg, positiveInt("a")); // "Something went wrong: The provided int was negative" fold(errMsg, succMsg, positiveInt("-3")); // "Found valid positive int: 123" fold(errMsg, succMsg, positiveInt("123"));
let flip: a e. t('a, 'e) => t('e, 'a);flipFlips the values between the success and error channels.flip(VOk(12)) == VError(12); flip(VError(-1)) == VOk(-1);
let map2: ('x => 'x => 'x) => ('a => 'b => 'c) => t('a, 'x) => t('b, 'x) => t('c, 'x);let map3: ('x => 'x => 'x) => ('a => 'b => 'c => 'd) => t('a, 'x) => t('b, 'x) => t('c, 'x) => t('d, 'x);let map4: ('x => 'x => 'x) => ('a => 'b => 'c => 'd => 'e) => t('a, 'x) => t('b, 'x) => t('c, 'x) => t('d, 'x) => t('e, 'x);let map5: ('x => 'x => '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);
module WithErrors: (Errors: BsBastet.Interface.SEMIGROUP_ANY) => (Error: BsBastet.Interface.TYPE) => { ... };