Last active
May 30, 2026 23:27
-
-
Save trvswgnr/c5f5f9731613379302b2eb5463bcba1a to your computer and use it in GitHub Desktop.
variable param ts hkt
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 const ARGS = Symbol("args"); | |
| export type ARGS = typeof ARGS; | |
| export const TYPE = Symbol("type"); | |
| export type TYPE = typeof TYPE; | |
| export const _F = Symbol("F"); | |
| export type _F = typeof _F; | |
| interface Parameterized<Args extends readonly unknown[]> { | |
| readonly [ARGS]: Args; | |
| } | |
| export interface TypeConstructor extends Parameterized<readonly unknown[]> { | |
| readonly [TYPE]: unknown; | |
| } | |
| export type Apply<F extends TypeConstructor, Args extends readonly unknown[]> = (F & | |
| Parameterized<Args>)[TYPE]; | |
| export interface Compose< | |
| F extends TypeConstructor, | |
| G extends TypeConstructor, | |
| > extends TypeConstructor { | |
| readonly [TYPE]: Apply<F, [Apply<G, [this[ARGS][0]]>]>; | |
| } | |
| export interface Flip<F extends TypeConstructor> extends TypeConstructor { | |
| readonly [TYPE]: Apply<F, [this[ARGS][1], this[ARGS][0]]>; | |
| } | |
| export interface Fix1<F extends TypeConstructor, A> extends TypeConstructor { | |
| readonly [TYPE]: Apply<F, [A, this[ARGS][0]]>; | |
| } | |
| export interface Fix2<F extends TypeConstructor, B> extends TypeConstructor { | |
| readonly [TYPE]: Apply<F, [this[ARGS][0], B]>; | |
| } | |
| export interface Fix12<F extends TypeConstructor, A, B> extends TypeConstructor { | |
| readonly [TYPE]: Apply<F, [A, B, this[ARGS][0]]>; | |
| } | |
| export interface Functor<F extends TypeConstructor, A> { | |
| readonly [_F]: F; | |
| map<B>(f: (a: A) => B): Apply<F, [B]>; | |
| } | |
| export interface Monad<F extends TypeConstructor, A> extends Functor<F, A> { | |
| flatMap<B>(f: (a: A) => Apply<F, [B]>): Apply<F, [B]>; | |
| } | |
| export interface Foldable<F extends TypeConstructor, A> { | |
| readonly [_F]: F; | |
| reduce<B>(b: B, f: (b: B, a: A) => B): B; | |
| } | |
| export interface Bifunctor<F extends TypeConstructor, A, B> { | |
| readonly [_F]: F; | |
| bimap<C, D>(f: (a: A) => C, g: (b: B) => D): Apply<F, [C, D]>; | |
| } | |
| export interface Applicative<F extends TypeConstructor> { | |
| of<A>(a: A): Apply<F, [A]>; | |
| map<A, B>(fa: Apply<F, [A]>, f: (a: A) => B): Apply<F, [B]>; | |
| ap<A, B>(fab: Apply<F, [(a: A) => B]>, fa: Apply<F, [A]>): Apply<F, [B]>; | |
| } | |
| export type NaturalTransformation<F extends TypeConstructor, G extends TypeConstructor> = <A>( | |
| fa: Apply<F, [A]>, | |
| ) => Apply<G, [A]>; | |
| export function liftA2<F extends TypeConstructor, A, B, C>( | |
| F: Applicative<F>, | |
| f: (a: A, b: B) => C, | |
| ): (fa: Apply<F, [A]>, fb: Apply<F, [B]>) => Apply<F, [C]> { | |
| return (fa, fb) => | |
| F.ap( | |
| F.map(fa, (a: A) => (b: B) => f(a, b)), | |
| fb, | |
| ); | |
| } | |
| export interface MaybeF extends TypeConstructor { | |
| readonly [TYPE]: MaybeInstance<this[ARGS][0]>; | |
| } | |
| // example implementations | |
| const NONE = Symbol("NONE"); | |
| type NONE = typeof NONE; | |
| class MaybeInstance<A> implements Monad<MaybeF, A>, Foldable<MaybeF, A> { | |
| declare readonly [_F]: MaybeF; | |
| readonly #inner: A | NONE; | |
| private constructor(inner: A | NONE) { | |
| this.#inner = inner; | |
| } | |
| // static methods (satisfy Applicative<MaybeF>) | |
| static of<A>(value: A): MaybeInstance<A> { | |
| return MaybeInstance.some(value); | |
| } | |
| static map<A, B>(fa: MaybeInstance<A>, f: (a: A) => B): MaybeInstance<B> { | |
| return fa.map(f); | |
| } | |
| static ap<A, B>(fab: MaybeInstance<(a: A) => B>, fa: MaybeInstance<A>): MaybeInstance<B> { | |
| return fab.flatMap((f) => fa.map(f)); | |
| } | |
| static some<A>(value: A): MaybeInstance<A> { | |
| return new MaybeInstance(value); | |
| } | |
| static none<A = never>(): MaybeInstance<A> { | |
| return new MaybeInstance<A>(NONE); | |
| } | |
| static fromNullable<A>(value: A | null | undefined): MaybeInstance<A> { | |
| return value === null || value === undefined ? MaybeInstance.none() : MaybeInstance.some(value); | |
| } | |
| // instance methods | |
| isSome(): boolean { | |
| return this.#inner !== undefined; | |
| } | |
| isNone(): boolean { | |
| return this.#inner === undefined; | |
| } | |
| fold<B>(onNone: () => B, onSome: (a: A) => B): B { | |
| return this.#inner === NONE ? onNone() : onSome(this.#inner); | |
| } | |
| map<B>(f: (a: A) => B): MaybeInstance<B> { | |
| return this.fold( | |
| () => MaybeInstance.none(), | |
| (a) => MaybeInstance.some(f(a)), | |
| ); | |
| } | |
| flatMap<B>(f: (a: A) => MaybeInstance<B>): MaybeInstance<B> { | |
| return this.fold(() => MaybeInstance.none(), f); | |
| } | |
| reduce<B>(b: B, f: (b: B, a: A) => B): B { | |
| return this.fold( | |
| () => b, | |
| (a) => f(b, a), | |
| ); | |
| } | |
| traverse<G extends TypeConstructor, B>( | |
| G: Applicative<G>, | |
| f: (a: A) => Apply<G, [B]>, | |
| ): Apply<G, [MaybeInstance<B>]> { | |
| return this.fold( | |
| () => G.of(MaybeInstance.none<B>()), | |
| (a) => G.map(f(a), (b: B) => MaybeInstance.some(b)), | |
| ); | |
| } | |
| getOrElse(fallback: A): A { | |
| return this.fold( | |
| () => fallback, | |
| (a) => a, | |
| ); | |
| } | |
| } | |
| export const Maybe = MaybeInstance satisfies Applicative<MaybeF>; | |
| export interface EitherF extends TypeConstructor { | |
| readonly [TYPE]: EitherInstance<this[ARGS][0], this[ARGS][1]>; | |
| } | |
| /** | |
| * The [_F] brand is Fix1<EitherF, E>, the monadic view with E fixed. | |
| * | |
| * For the Applicative static interface, Either provides typed static | |
| * methods. Since Either.of returns Either<never, A> and never is | |
| * bottom, the result is assignable to Either<E, A> for all E. | |
| */ | |
| class EitherInstance<E, A> implements Monad<Fix1<EitherF, E>, A>, Foldable<Fix1<EitherF, E>, A> { | |
| declare readonly [_F]: Fix1<EitherF, E>; | |
| private readonly _left: E | NONE; | |
| private readonly _right: A | NONE; | |
| private constructor(left: E | NONE, right: A | NONE) { | |
| this._left = left; | |
| this._right = right; | |
| } | |
| // static methods (satisfy Applicative<EitherF>) | |
| static of<A>(a: A): EitherInstance<never, A> { | |
| return EitherInstance.right(a); | |
| } | |
| static map<E, A, B>(fa: EitherInstance<E, A>, f: (a: A) => B): EitherInstance<E, B> { | |
| return fa.map(f); | |
| } | |
| static ap<E, A, B>( | |
| fab: EitherInstance<E, (a: A) => B>, | |
| fa: EitherInstance<E, A>, | |
| ): EitherInstance<E, B> { | |
| return fab.flatMap((f) => fa.map(f)); | |
| } | |
| static left<E, A = never>(e: E): EitherInstance<E, A> { | |
| return new EitherInstance<E, A>(e, NONE); | |
| } | |
| static right<A, E = never>(a: A): EitherInstance<E, A> { | |
| return new EitherInstance<E, A>(NONE, a); | |
| } | |
| // instance methods | |
| fold<B>(onLeft: (e: E) => B, onRight: (a: A) => B): B { | |
| if (this._right !== NONE) { | |
| return onRight(this._right); | |
| } | |
| if (this._left !== NONE) { | |
| return onLeft(this._left); | |
| } | |
| throw new Error("unreachable: Either is neither Left nor Right"); | |
| } | |
| map<B>(f: (a: A) => B): EitherInstance<E, B> { | |
| return this.fold( | |
| (e) => EitherInstance.left(e), | |
| (a) => EitherInstance.right(f(a)), | |
| ); | |
| } | |
| flatMap<B>(f: (a: A) => EitherInstance<E, B>): EitherInstance<E, B> { | |
| return this.fold((e) => EitherInstance.left(e), f); | |
| } | |
| bimap<C, D>(f: (e: E) => C, g: (a: A) => D): EitherInstance<C, D> { | |
| return this.fold( | |
| (e) => EitherInstance.left(f(e)), | |
| (a) => EitherInstance.right(g(a)), | |
| ); | |
| } | |
| reduce<B>(b: B, f: (b: B, a: A) => B): B { | |
| return this.fold( | |
| () => b, | |
| (a) => f(b, a), | |
| ); | |
| } | |
| traverse<G extends TypeConstructor, B>( | |
| G: Applicative<G>, | |
| f: (a: A) => Apply<G, [B]>, | |
| ): Apply<G, [EitherInstance<E, B>]> { | |
| return this.fold( | |
| (e) => G.of(EitherInstance.left<E, B>(e)), | |
| (a) => G.map(f(a), (b: B) => EitherInstance.right(b)), | |
| ); | |
| } | |
| getOrElse(fallback: A): A { | |
| return this.fold( | |
| () => fallback, | |
| (a) => a, | |
| ); | |
| } | |
| } | |
| export const Either = EitherInstance satisfies Applicative<Fix1<EitherF, unknown>>; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment