Last active
February 5, 2025 01:10
-
-
Save SimonMeskens/1ffed50143636def6bc2cd5afd7e9650 to your computer and use it in GitHub Desktop.
Higher Kinded Types in TypeScript
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
// Copyright 2021 Simon Meskens | |
// Licensed under the MIT License | |
// | |
// Permission to use, copy, modify, and/or distribute this software for any | |
// purpose with or without fee is hereby granted, provided this license is | |
// preserved. This software is offered as-is, without any warranty. | |
// Functor | |
interface StaticFunctor<G> { | |
map<F extends Generic<G>, U>( | |
transform: (a: Params<F>[0]) => U, | |
mappable: F | |
): Generic<F, [U]>; | |
} | |
// Examples | |
const arrayFunctor: StaticFunctor<unknown[]> = { | |
map: (fn, fa) => { | |
return fa.map(fn); | |
} | |
}; | |
const objectFunctor: StaticFunctor<object> = { | |
map: (fn, fa) => { | |
return fn(fa); | |
} | |
}; | |
const nullableFunctor: StaticFunctor<object | null | undefined> = { | |
map: (fn, fa) => { | |
return fa == undefined ? fa : fn(fa); | |
} | |
}; | |
const stringify = (x: number) => JSON.stringify(x); | |
const xs = arrayFunctor.map(stringify, [4, 2]); // xs: string[] | |
const x = objectFunctor.map(stringify, 42); // x: string | |
const xNull = nullableFunctor.map(stringify, null); // xNull: null | |
const xSome = nullableFunctor.map(stringify, 4 as number | undefined); // xSome: string | undefined | |
const functor: StaticFunctor<unknown | unknown[]> = { | |
map(fn, fa) { | |
return Array.isArray(fa) | |
? arrayFunctor.map(fn, fa) | |
: fa != undefined | |
? objectFunctor.map(fn, fa) | |
: nullableFunctor.map(fn, fa); | |
} | |
}; | |
const ys = functor.map(stringify, [4, 2]); // ys: string[] | |
const y = functor.map(stringify, 42); // y: string | |
const yNull = functor.map(stringify, null); // yNull: null | |
const ySome = functor.map(stringify, 42 as number | undefined); // ySome: string | undefined | |
// Plumbing | |
interface TypeProps<T = unknown, Params extends ArrayLike<unknown> = never> { | |
array: { | |
infer: T extends Array<infer A> ? [A] : never; | |
construct: Params[0][]; | |
}; | |
null: { | |
infer: null extends T ? [never] : never; | |
construct: null; | |
}; | |
undefined: { | |
infer: undefined extends T ? [never] : never; | |
construct: undefined; | |
}; | |
object: { | |
infer: [NonNullable<T>]; | |
construct: Params[0]; | |
}; | |
} | |
type Match<T> = T extends infer U | |
? (unknown extends U | |
? any | |
: TypeProps<U>[Exclude< | |
keyof TypeProps, | |
"object" | |
>]["infer"]) extends never | |
? "object" | |
: { | |
[Key in Exclude<keyof TypeProps, "object">]: TypeProps<T>[Key]["infer"] extends never | |
? never | |
: Key | |
}[Exclude<keyof TypeProps, "object">] | |
: never; | |
type Params<T> = TypeProps<T>[Match<T>]["infer"]; | |
type Generic<T, Params extends ArrayLike<any> = ArrayLike<any>> = TypeProps< | |
T, | |
Params | |
>[Match<T>]["construct"]; | |
// Test output | |
console.log("TAP version 13"); | |
console.log("# TypeProps Proof of Concept"); | |
console.log("1..8"); | |
console.log( | |
xs[0] === "4" && xs[1] === "2" ? "ok 1 array map" : "not ok 1 array map" | |
); | |
console.log(x === "42" ? "ok 2 object map" : "not ok 2 object map"); | |
console.log(xNull === null ? "ok 3 null map" : "not ok 3 null map"); | |
console.log(xSome === "4" ? "ok 4 some map" : "not ok 4 some map"); | |
console.log( | |
ys[0] === "4" && ys[1] === "2" | |
? "ok 5 generic array map" | |
: "not ok 5 generic array map" | |
); | |
console.log( | |
y === "42" ? "ok 6 generic object map" : "not ok 6 generic object map" | |
); | |
console.log( | |
yNull === null ? "ok 7 generic null map" : "not ok 7 generic null map" | |
); | |
console.log( | |
ySome === "42" | |
? "ok 8 generic nullable map" | |
: "not ok 8 generic nullable map" | |
); |
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
// Copyright 2021 Simon Meskens | |
// Licensed under the MIT License | |
// | |
// Permission to use, copy, modify, and/or distribute this software for any | |
// purpose with or without fee is hereby granted, provided this license is | |
// preserved. This software is offered as-is, without any warranty. | |
// Examples | |
const arrayFunctor = { | |
map: (fn, fa) => { | |
return fa.map(fn); | |
} | |
}; | |
const objectFunctor = { | |
map: (fn, fa) => { | |
return fn(fa); | |
} | |
}; | |
const nullableFunctor = { | |
map: (fn, fa) => { | |
return fa == undefined ? fa : fn(fa); | |
} | |
}; | |
const stringify = x => JSON.stringify(x); | |
const xs = arrayFunctor.map(stringify, [4, 2]); // xs: string[] | |
const x = objectFunctor.map(stringify, 42); // x: string | |
const xNull = nullableFunctor.map(stringify, null); // xNull: null | |
const xSome = nullableFunctor.map(stringify, 4 as number | undefined); // xSome: string | undefined | |
const functor = { | |
map(fn, fa) { | |
return Array.isArray(fa) | |
? arrayFunctor.map(fn, fa) | |
: fa != undefined | |
? objectFunctor.map(fn, fa) | |
: nullableFunctor.map(fn, fa); | |
} | |
}; | |
const ys = functor.map(stringify, [4, 2]); // ys: string[] | |
const y = functor.map(stringify, 42); // y: string | |
const yNull = functor.map(stringify, null); // yNull: null | |
const ySome = functor.map(stringify, 42 as number | undefined); // ySome: string | undefined | |
// Test output | |
console.log("TAP version 13"); | |
console.log("# TypeProps Proof of Concept"); | |
console.log("1..8"); | |
console.log( | |
xs[0] === "4" && xs[1] === "2" ? "ok 1 array map" : "not ok 1 array map" | |
); | |
console.log(x === "42" ? "ok 2 object map" : "not ok 2 object map"); | |
console.log(xNull === null ? "ok 3 null map" : "not ok 3 null map"); | |
console.log(xSome === "4" ? "ok 4 some map" : "not ok 4 some map"); | |
console.log( | |
ys[0] === "4" && ys[1] === "2" | |
? "ok 5 generic array map" | |
: "not ok 5 generic array map" | |
); | |
console.log( | |
y === "42" ? "ok 6 generic object map" : "not ok 6 generic object map" | |
); | |
console.log( | |
yNull === null ? "ok 7 generic null map" : "not ok 7 generic null map" | |
); | |
console.log( | |
ySome === "42" | |
? "ok 8 generic nullable map" | |
: "not ok 8 generic nullable map" | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment