Skip to main content

fp

Higher-kinded-type encoding — Fn, Apply, MapTuple, Reduce, ComposeFn.

import type {
  Fn, Apply,
  Identity, Constant,
  MapTuple, FilterTuple, Reduce,
  ComposeFn, FlipFn, PartialFn,
  Stringify, Singleton, FnNot,
} from '@gentleduck/ttest/fp'

Higher-kinded types in TypeScript via a Fn interface: any type-function is an interface extending Fn whose return member references this['arg']. Apply<F, X> binds X into the slot and evaluates return.

Fn

interface Fn {
  readonly arg: unknown
  readonly return: unknown
}

Base interface. Subclasses override return.

Apply

type Apply<F extends Fn, X> = (F & { readonly arg: X })['return']

Evaluate a type-function by binding X into its arg slot.

interface ToString extends Fn { readonly return: `${this['arg'] & (string | number)}` }
type a = Apply<ToString, 42>  // '42'

Identity

interface Identity extends Fn { readonly return: this['arg'] }

Apply<Identity, X> = X.

Constant

interface Constant<C> extends Fn { readonly return: C }

Always returns C, ignoring input.

MapTuple

type MapTuple<T extends readonly unknown[], F extends Fn> = { [K in keyof T]: Apply<F, T[K]> }

Map every element of T through F.

type a = MapTuple<[1, 2, 3], ToString>  // ['1', '2', '3']

FilterTuple

type FilterTuple<T extends readonly unknown[], F extends Fn, Acc extends unknown[] = []> =
  T extends readonly [infer H, ...infer R]
    ? Apply<F, H> extends true
      ? FilterTuple<R, F, [...Acc, H]>
      : FilterTuple<R, F, Acc>
    : Acc

Keep only elements for which F returns true.

interface IsNumeric extends Fn { readonly return: this['arg'] extends number ? true : false }
type a = FilterTuple<[1, 'a', 2, 'b'], IsNumeric>  // [1, 2]

Reduce

type Reduce<T extends readonly unknown[], Init, F extends Fn> =
  T extends readonly [infer H, ...infer R]
    ? Reduce<R, Apply<F, [Init, H]>, F>
    : Init

Left-fold T with accumulator Init. F receives [Acc, Item] in its arg slot.

type BuildTuple<N extends number, Acc extends unknown[] = []> =
  Acc['length'] extends N ? Acc : BuildTuple<N, [...Acc, unknown]>

interface Add extends Fn {
  readonly return: this['arg'] extends [infer A extends number, infer B extends number]
    ? [...BuildTuple<A>, ...BuildTuple<B>]['length']
    : never
}

ComposeFn

interface ComposeFn<G extends Fn, F extends Fn> extends Fn {
  readonly return: Apply<G, Apply<F, this['arg']>>
}

Right-to-left composition. Apply<ComposeFn<G, F>, X> = Apply<G, Apply<F, X>>.

FlipFn

interface FlipFn<F extends Fn> extends Fn {
  readonly return: this['arg'] extends [infer A, infer B] ? Apply<F, [B, A]> : never
}

Flip the argument order of a binary tuple-arg function.

PartialFn

interface PartialFn<F extends Fn, A> extends Fn {
  readonly return: Apply<F, [A, this['arg']]>
}

Partially apply the first argument of a 2-ary function.

Stringify

interface Stringify extends Fn {
  readonly return: `${this['arg'] & (string | number | bigint | boolean)}`
}

Convert any toString-able value to a string literal.

Singleton

interface Singleton extends Fn { readonly return: [this['arg']] }

Wrap a value into a single-element tuple.

FnNot

interface FnNot extends Fn { readonly return: this['arg'] extends true ? false : true }

Negate a boolean.