Skip to content

Instantly share code, notes, and snippets.

@wycats
Last active April 8, 2018 02:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save wycats/322283793cdf74a61ef30965adeab428 to your computer and use it in GitHub Desktop.
Save wycats/322283793cdf74a61ef30965adeab428 to your computer and use it in GitHub Desktop.
import { unknown } from 'ts-std';
import { ErrorMessage } from "monad-test";
export type ErrorPath = ReadonlyArray<string>;
export interface ErrorMessage {
key: string;
args: unknown;
}
QUnit.module('monads');
QUnit.test('a stack', assert => {
let first = result<number[], undefined>(undefined);
let [ , init ] = first([]);
let [ , pushed1 ] = push(1)(init);
assert.deepEqual(pushed1, [1]);
let [ , pushed2 ] = push(2)(pushed1);
assert.deepEqual(pushed2, [1, 2]);
let [ popped, array ] = pop()(pushed2);
assert.deepEqual(popped, 2);
assert.deepEqual(array, [1]);
});
QUnit.test('object wrapper', assert => {
class Stack {
private inner = result<number[], undefined | number>(undefined)([]);
get state(): number[] {
return this.inner[1];
}
get popped(): number | undefined {
return this.inner[0];
}
push(value: number): void {
this.inner = push(value)(this.state);
}
pop(): number | undefined {
this.inner = pop<number>()(this.state);
return this.popped;
}
}
let s = new Stack();
s.push(1);
assert.deepEqual(s.state, [1]);
s.push(2);
assert.deepEqual(s.state, [1, 2]);
s.pop();
assert.deepEqual(s.popped, 2);
assert.deepEqual(s.state, [1]);
});
QUnit.test('validations', assert => {
let { errors } = presence()(1);
assert.deepEqual(errors, []);
errors = presence()(null).errors;
assert.deepEqual(errors, [{ key: 'presence', args: null }]);
mapValid(presence(), format(/hello/));
let validator = mapValid(mapValid(presence(), str()), format(/hello/));
errors = validator(null).errors;
assert.deepEqual(errors, [{ key: 'presence', args: null }]);
errors = validator(1).errors;
assert.deepEqual(errors, [{ key: 'string', args: null }]);
errors = validator('world').errors;
assert.deepEqual(errors, [{ key: 'format', args: /hello/ }]);
errors = validator('hello').errors;
assert.deepEqual(errors, []);
let matchesHello = mapError(validator, () => [{ key: 'matches-hello', args: null }]);
errors = matchesHello('world').errors;
assert.deepEqual(errors, [{ key: 'matches-hello', args: null }]);
errors = matchesHello('hello').errors;
assert.deepEqual(errors, []);
let stringOrNumber = mapError(or(str(), num()), err => [{ key: 'index', args: { cause: err } }]);
errors = stringOrNumber(null).errors;
assert.deepEqual(errors, [{ key: 'index', args: { cause: [{ key: 'string', args: null }] } }])
errors = stringOrNumber(0).errors;
assert.deepEqual(errors, [])
errors = stringOrNumber('hello').errors;
assert.deepEqual(errors, [])
errors = stringOrNumber(false).errors;
assert.deepEqual(errors, [{ key: 'index', args: { cause: [{ key: 'string', args: null }] } }]);
////
type Result<V> = { value: V, errors: never[] } | { value: unknown, errors: ErrorMessage[] };
type Validation<V, V2 = V> = (value: V) => Result<V2>;
function wrap<V>(result: Result<V>): Validation<V> {
return () => result;
}
function mapError<V, V2>(validate: Validation<V, V2>, transform: (error: ErrorMessage[]) => ErrorMessage[]): Validation<V, V2> {
return mapResult(validate, result => {
if (result.errors.length === 0) {
return result;
} else {
return { value: result.value, errors: transform(result.errors) };
}
});
}
function mapValid<V, V2, V3>(validate: Validation<V, V2>, transform: (value: V2) => Result<V3>): Validation<V, V3> {
return mapResult(validate, result => {
if (result.errors.length === 0) {
return transform(result.value as V2);
} else {
return result;
}
});
}
function mapResult<V, V2, V3>(validate: Validation<V, V2>, transform: (result: Result<V2>) => Result<V3>): Validation<V, V3> {
return (value: V) => {
let result = validate(value);
return transform(result);
}
// in terms of then:
// return then(validate, result => {
// return wrap(transform(result));
// });
}
function or<In, OutA, OutB>(a: Validation<In, OutA>, b: Validation<In, OutB>): Validation<In, OutA | OutB> {
return then(a, resultA => {
let aErrors = resultA.errors.length > 0;
if (!aErrors) return wrap(resultA);
return then(b, resultB => {
let bErrors = resultB.errors.length > 0;
if (!bErrors) return wrap(resultB);
return wrap(resultA);
});
});
}
function then(validate: Validation<unknown>, transform: (result: Result<unknown>) => Validation<unknown>): Validation<unknown> {
return (value: unknown) => {
let result = validate(value);
return transform(result)(value);
};
}
function str(): Validation<unknown, string> {
return (value: unknown): Result<string> => {
if (typeof value === 'string') {
return { value, errors: [] };
} else {
return { value, errors: [{ key: 'string', args: null }] };
}
}
}
function num(): Validation<unknown, number> {
return (value: unknown): Result<number> => {
if (typeof value === 'number') {
return { value, errors: [] };
} else {
return { value, errors: [{ key: 'number', args: null }] };
}
}
}
function format(regexp: RegExp): Validation<string, string> {
return (value: string) => {
if (value.match(regexp)) {
return { value, errors: [] };
} else {
return { value, errors: [{ key: 'format', args: regexp }] };
}
}
}
function presence(): Validation<unknown, unknown> {
return (value: unknown): Result<unknown> => {
if (value === null) {
return { value, errors: [{ key: 'presence', args: null }] };
} else {
return { value, errors: [] };
}
}
}
});
export type MonadicValue<V, S1, S2 = S1> = (state: S1) => [V, S2];
export type MonadicFunction<V, S> = (value: V) => MonadicValue<V, S, S>;
export function result<S, V>(value: V): MonadicValue<V, S, S> {
return (state: S) => [value, state];
}
export function bind<V, S1, S2>(value: MonadicValue<V, S1, S2>, func: MonadicFunction<V, S2>): MonadicValue<V, S1, S2> {
return function(state: S1) {
let compute = value(state);
let val = compute[0];
let newState = compute[1];
return func(val)(newState);
}
}
export function push<V>(value: V): MonadicValue<undefined, V[], V[]> {
return (state: V[]) => [undefined, [...state, value]];
}
export function pop<V>(): MonadicValue<V, V[], V[]> {
return (state: V[]) => {
let newState = state.slice(0, -1);
return [state[state.length - 1], newState];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment