Skip to content

Instantly share code, notes, and snippets.

@trvswgnr
Last active May 30, 2026 23:27
Show Gist options
  • Select an option

  • Save trvswgnr/c5f5f9731613379302b2eb5463bcba1a to your computer and use it in GitHub Desktop.

Select an option

Save trvswgnr/c5f5f9731613379302b2eb5463bcba1a to your computer and use it in GitHub Desktop.
variable param ts hkt
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