Skip to content

Instantly share code, notes, and snippets.

@tstelzer
Last active January 26, 2019 17:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tstelzer/c0a0b3f9860d6ff10f2de45215b9b14a to your computer and use it in GitHub Desktop.
Save tstelzer/c0a0b3f9860d6ff10f2de45215b9b14a to your computer and use it in GitHub Desktop.
an attempt at applicative validation
import {array} from 'fp-ts/lib/Array';
import {getArrayMonoid} from 'fp-ts/lib/Monoid';
import {
failure as _failure,
Failure as _Failure,
getApplicative,
isSuccess as _isSuccess,
success as _success,
Success as _Success,
Validation as _Validation,
} from 'fp-ts/lib/Validation';
/** A expected failure message. */
export type Message = {
/** What were we doing when the Failure occured. */
context: string;
/** A description of the Failure. */
description: string;
};
/** Represents a failed Validation. */
export type Failure<M> = _Failure<Message[], M>;
/** Represents a successful Validation. */
export type Success<M> = _Success<Message[], M>;
/** Either a Success or a Failure. */
export type Validation<A> = _Validation<Message[], A>;
/** A function validating some value. */
export type Validator<A> = (v: A) => Validation<A>;
/** Returns a Validation Failure. */
export const fail = <A>(message: Message): Validation<A> => _failure([message]);
/** Returns a Validation Success. */
export const pass = <A>(value: A): Validation<A> => _success(value);
/** Asserts that Validation succeeded. */
export const isSuccess = _isSuccess;
/** Instance of an Applicative Monoid. */
const validation = getApplicative(getArrayMonoid<Message>());
/** Traverse over validation. */
const traverse = array.traverse(validation);
/** Generate failure message. */
export const message = (context: string) => (description: string): Message => ({
description,
context,
});
/**
* Takes a record of a `predicate` and a `message` and runs the `predicate`
* against some `value`, returning a `Validation`. If you need more control
* over generating failure messages (i.e. the signature of `message` is too
* simplistic), or you want to handle predicates differently, simply write your
* own `Validator` by using the `pass` and `fail` functions.
*/
export const validate = <A>({
predicate,
message,
}: {
predicate: (value: A) => boolean;
message: (value: A) => Message;
}): Validator<A> => (value: A) =>
predicate(value) ? pass(value) : fail(message(value));
/**
* Takes a list of `Validator`s and returns a new `Validator` which will apply
* every one of the original `Validator`s to some `value`.
*
* @sig :: [(a -> Validation a)] -> a -> Validation a
*/
export const allPass = <A>(checks: Array<Validator<A>>): Validator<A> => (
value: A,
) => traverse(checks, f => f(value)).map(() => value);
export function pipe<A, B, C>(
ab: (value: A) => Validation<B>,
bc: (value: B) => Validation<C>,
): (a: A) => Validation<C>;
export function pipe<A, B, C, D>(
ab: (value: A) => Validation<B>,
bc: (value: B) => Validation<C>,
cd: (value: C) => Validation<D>,
): (a: A) => Validation<D>;
export function pipe<A, B, C, D, E>(
ab: (value: A) => Validation<B>,
bc: (value: B) => Validation<C>,
cd: (value: C) => Validation<D>,
de: (value: D) => Validation<E>,
): (a: A) => Validation<E>;
/**
* Performs left-right composition of `Validator`s. First `Failure` is
* short-circuited.
*
* @sig :: (a -> Validation b) -> ... -> (a -> Validation n)
*/
export function pipe(
...fns: Array<Validator<any>>
): (value: any) => Validation<any> {
const length = fns.length - 1;
return function(this: any, value: any) {
let result = pass(value);
for (let i = 0; i <= length; i++) {
if (!isSuccess(result)) {
break;
}
result = fns[i].call(this, result.value);
}
return result;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment