Skip to content

Instantly share code, notes, and snippets.

@reidev275
Created July 25, 2019 12:03
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/7026c644dd831ae1e1878c804f4f9755 to your computer and use it in GitHub Desktop.
Save reidev275/7026c644dd831ae1e1878c804f4f9755 to your computer and use it in GitHub Desktop.
import { Monoid } from "./monoid";
import { Validation, validationMonoid, success, failure } from "./validation";
import { PredValidation, contramap } from "./predValidation";
export type AsyncPredValidation<A> = (a: A) => Promise<Validation>;
// A way to turn a PredValidation into an AsyncPredValidation
export const lift = <A>(v: PredValidation<A>): AsyncPredValidation<A> => (
a: A
) => Promise.resolve(v(a));
const asyncPredValidationMonoid = <A>(): Monoid<AsyncPredValidation<A>> => ({
empty: () => Promise.resolve(validationMonoid.empty),
append: (x, y) => (a: A) =>
Promise.all([x(a), y(a)]).then(([v1, v2]) =>
validationMonoid.append(v1, v2)
)
});
export const combine = <A>(
...as: AsyncPredValidation<A>[]
): AsyncPredValidation<A> => {
const M = asyncPredValidationMonoid<A>();
return as.reduce(M.append, M.empty);
};
//simple validation
const positive = (n: number): Validation =>
n > 0 ? success : failure([`Input should be positive: ${n}`]);
//primitive validation requiring a lookup from an external resource
const nameValidation = (s: string): Promise<Validation> =>
Promise.resolve(success);
//non primitive type
type Person = {
first: string;
last: string;
age: number;
};
// combining an asyncPredValidation with a predValidation
// by lifting the predValidation into an asyncPredValidation
const personValidation = combine(
contramap((x: Person) => x.first, nameValidation),
lift(contramap((x: Person) => x.age, positive))
);
personValidation({
first: "Reid",
last: "Evans",
age: 37
}).then(console.log);
// { kind: "Success" }
personValidation({
first: "Reid",
last: "Evans",
age: -2
}).then(console.log);
// { kind: "Failure", errors: [ "Input should be positive: -2" ] }
export interface Monoid<A> {
empty: A;
append(x: A, y: A): A;
}
import { Monoid } from "./monoid";
import { Validation, validationMonoid, success, failure } from "./validation";
export type PredValidation<A> = (a: A) => Validation;
// proof we can combine 0-many PredValidation<A> types in such
// a way that all validations must pass
const predValidationMonoid = <A>(): Monoid<PredValidation<A>> => ({
empty: () => validationMonoid.empty,
append: (x, y) => (a: A) => validationMonoid.append(x(a), y(a))
});
// a way to make primitive validation rules work for a non primitive type
export const contramap = <A, B, C>(
f: (b: B) => A,
v: (a: A) => C
): ((b: B) => C) => (b: B) => v(f(b));
// a way to combining multiple validation rules into a single validation rule
export const combine = <A>(...as: PredValidation<A>[]): PredValidation<A> => {
const M = predValidationMonoid<A>();
return as.reduce(M.append, M.empty);
};
//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 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" ] }
import { Monoid } from "./monoid";
// a type to contain information about validation results
export type Validation =
| { kind: "Success" }
| { kind: "Failure"; errors: string[] };
// helper functions to create Validation objects
export const success: Validation = { kind: "Success" };
export const failure = (errors: string[]): Validation => ({
kind: "Failure",
errors: errors
});
export const validationMonoid: Monoid<Validation> = {
empty: success,
append: (x, y) => {
if (x.kind === "Success" && y.kind === "Success") return x;
let errors = [];
if (x.kind === "Failure") {
errors = [...errors, ...x.errors];
}
if (y.kind === "Failure") {
errors = [...errors, ...y.errors];
}
return failure(errors);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment