Skip to content

Instantly share code, notes, and snippets.

@reidev275
Last active July 24, 2019 14:08
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 reidev275/a99bfe785ec65ba4d23d51e3e84b99c3 to your computer and use it in GitHub Desktop.
Save reidev275/a99bfe785ec65ba4d23d51e3e84b99c3 to your computer and use it in GitHub Desktop.
Validation with Monoidal Contravariant Functors
// a type to contain information about validation results
export type Validation =
| { kind: "Success" }
| { kind: "Failure"; errors: string[] };
// helper functions to create Validation objects
const success: Validation = { kind: "Success" };
const failure = (errors: string[]): Validation => ({
kind: "Failure",
errors: errors
});
type PredValidation<A> = (a: A) => Validation;
// proof we can combine 0-many PredValidation<A> types in such
// a way that all validations must pass to be successful
// otherwise aggregate all errors
const predValidationMonoid = <A>() => ({
empty: () => success,
append: (x, y) => (a: A) => {
const v1 = x(a);
const v2 = y(a);
let errors = [];
if (v1.kind === "Failure") {
errors = [...errors, ...v1.errors];
}
if (v2.kind === "Failure") {
errors = [...errors, ...v2.errors];
}
if (v1.kind === "Success" && v2.kind === "Success") return v1;
return failure(errors);
}
});
//primitive validation rules
const maxLength = (maxLength: number) => (s: string): Validation =>
s.length < maxLength
? success
: failure([`Input exceeds ${maxLength} characters: ${s}`]);
const positive = (n: number): Validation =>
n > 0 ? success : failure([`Input should be positive: ${n}`]);
//non primitive type
type Person = {
first: string;
last: string;
age: number;
};
// a way to make primitive validation rules work for a non primitive type
const contramap = <A, B>(
f: (b: B) => A,
v: PredValidation<A>
): PredValidation<B> => (b: B) => v(f(b));
// a way to combining multiple validation rules into a single validation rule
const combine = <A>(...as: PredValidation<A>[]): PredValidation<A> => {
const M = predValidationMonoid<A>();
return as.reduce(M.append, M.empty);
};
// a compound validation rule
const personValidation = combine(
contramap((x: Person) => x.age, positive),
contramap((x: Person) => x.first, maxLength(20)),
contramap((x: Person) => x.last, maxLength(25))
);
//
// examples
const valid = personValidation({
first: "Reid",
last: "Evans",
age: 2
});
// { kind: "Success" }
const invalid = personValidation({
first: "Reid",
last: "Evans",
age: -2
});
// { kind: "Failure", errors: [ "Input should be positive: -2" ] }
const invalidTwice = personValidation({
first: "this is a really long name",
last: "Evans",
age: -2
});
// { kind: "Failure", errors: [ "Input should be positive: -2", "Input exceeds 20 characters: this is a really long name" ] }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment