Created
July 25, 2019 12:03
-
-
Save reidev275/7026c644dd831ae1e1878c804f4f9755 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ] } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface Monoid<A> { | |
empty: A; | |
append(x: A, y: A): A; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ] } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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