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.