Skip to content

Instantly share code, notes, and snippets.

@bradparker
Last active July 25, 2019 10:35
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 bradparker/813774ad056009c194efb8c5d0a3749d to your computer and use it in GitHub Desktop.
Save bradparker/813774ad056009c194efb8c5d0a3749d to your computer and use it in GitHub Desktop.
Refinement type-y kinda things
interface Refinement<A> {
tag: string;
value: A;
}
interface Refined<A, R> {
value: A;
refinement: R;
}
const refine = <V, A extends Refinement<V>, B extends Refinement<V>>(
refinerA: (a: V) => A | null,
refinerB: (a: V) => B | null
) => (value: V): Refined<V, A & B> | null => {
const refinementA = refinerA(value);
if (refinementA === null) return null;
const refinementB = refinerB(value);
if (refinementB === null) return null;
return {
value,
refinement: { ...refinementA, ...refinementB }
};
};
interface LessThan<N extends number> {
tag: "LessThan";
limit: N;
value: number;
}
const lessThan = <N extends number>(limit: N) => (
value: number
): LessThan<N> | null => {
if (value > limit) return null;
return { tag: "LessThan", limit, value };
};
interface GreaterThan<N extends number> {
tag: "GreaterThan";
limit: N;
value: number;
}
const greaterThan = <N extends number>(limit: N) => (
value: number
): GreaterThan<N> | null => {
if (value < limit) return null;
return { tag: "GreaterThan", limit, value };
};
interface LengthLessThan<A, N extends number> {
tag: "LengthLessThan";
limit: N;
value: A;
}
const lengthLessThan = <N extends number, A extends { length: number }>(
limit: N
) => (value: A): LengthLessThan<A, N> | null => {
if (value.length > limit) return null;
return { tag: "LengthLessThan", limit, value };
};
interface LengthGreaterThan<A, N extends number> {
tag: "LengthGreaterThan";
limit: N;
value: A;
}
const lengthGreaterThan = <N extends number, A extends { length: number }>(
limit: N
) => (value: A): LengthGreaterThan<A, N> | null => {
if (value.length < limit) return null;
return { tag: "LengthGreaterThan", limit, value };
};
type Age = Refined<number, GreaterThan<8> & LessThan<200>>;
const age: (a: number) => Age | null = refine(greaterThan(8), lessThan(200));
type Username = Refined<
string,
LengthGreaterThan<string, 1> & LengthLessThan<string, 20>
>;
const username: (a: string) => Username | null = refine(
lengthGreaterThan(1),
lengthLessThan(20)
);
interface User {
age: Age;
username: Username;
}
const user = (a: number, u: string): User | null => {
const refinedAge = age(a);
const refinedUsername = username(u);
if (refinedAge === null || refinedUsername === null) {
return null;
}
return { age: refinedAge, username: refinedUsername };
};
const main = () => {
const one = user(33, "Not too long");
console.log(`One: ${JSON.stringify(one)}`);
const two = user(33, "Waaaaaaaaaaaaaaaaaaaaaaaayyyyyy too long");
console.log(`Two: ${JSON.stringify(two)}`);
};
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment