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)
returnstrue
ifv
is of the formVOk(val)
;false
otherwise.
let isError: a e. t('a, 'e) => bool;
isError(x)
returnstrue
ifx
is of the formVError(val)
;false
otherwise.
let map: a b e. ('a => 'b) => t('a, 'e) => t('b, 'e);
map(f, x)
returnsVOk(f(x))
ifx
is of the formVOk(v)
. It returnsVError(e)
ifx
is 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. Ifx
is 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)
ifx
is of the formVOk(v)
. It returnsVError(f(e))
ifx
is 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.Array
of 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.List
of 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. Ifx
is 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))
ifx
is of the formVOk(v)
; it returnsVError(g(e))
ifx
is 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. Ifx
is of the formVOk(v)
,bitap()
callsf(v)
; ifx
is 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
valFcn
is of the formVOk(f)
, functionf
is applied tox
. Ifx
isVOk(v)
, the result isVOk(f(v))
. Ifx
isVError(err)
, the error is passed onwards.If
valFcn
is itself of the formVError(err)
andx
isVOk(v)
,VError(err)
is passed on.Finally, if both
valFcn
andx
areVError(e1)
andVError(e2)
, the result isVError(appendErrFcn(e1, e2))
.Using
apply()
properly is somewhat complex. See the example in the__tests__/Relude_Validation_test.re
file 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)
whenx
is of the formVOk(v)
, and returnsx
unchanged when it is of the formVError(v)
.Note:
Validation
is not a traditional monad in that it's purpose is to collect errors during applicative validation. UsingflatMap
will 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);
bind
is the same asflatMap
with the arguments flipped.
let fromResult: Pervasives.result('a, 'b) => t('a, 'b);
fromResult
converts a value of typeresult
to 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);
toResult
converts a value of typeValidation.t
toresult
.
let fromOption: a e. 'e => option('a) => t('a, 'e);
fromOption
converts anoption
into 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);
fromOptionLazy
converts anoption
into 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);
flip
Flips 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) => { ... };