A functional composition helper for TypesScript that provides type safety without the need for the usual countless overloads, thanks to some functional type trickery.
This module contains just 4 curried functions: c
, o
, p
, b
.
copb
is available on npm as well as https://deno.land/x/copb.
npm install --save copb
b
is a simple curried binary composition function, which the other
functions use under the hood.
import { b } from "https://deno.land/x/copb/mod.ts";
const f = (x: number) => x / 4;
const g = (x: number) => x - 5;
// h = f ∘ g
const h = b(f)(g);
console.log(h(13)); // -> 2
c
for callable is used to build compositional stacks generated by o
and
p
. Once built, the resultant composed function can be applied in the normal
way. See usage in the sections below.
o
(named after the mathematical composition symbol) is for more complex
compositions, with the ability to compose any number of uniary functions
together.
import { b } from "https://deno.land/x/copb/mod.ts";
const f = (x: string) => "number " + x;
const g = (x: number) => String(x);
const h = (x: number) => x / 3;
const l = (x: number) => x - 6;
// m = f ∘ g ∘ h ∘ l
const m = c(o(f)(g)(h)(l));
console.log(m(15)); // -> "number 3"
p
for pipeline is used to pipe the results of functions to each other. It is
identical to o
except it is read from left to right.
import { b } from "https://deno.land/x/copb/mod.ts";
const f = (x: string) => "number " + x;
const g = (x: number) => String(x);
const h = (x: number) => x / 3;
const l = (x: number) => x - 6;
// m = f ∘ g ∘ h ∘ l
const m = c(p(l)(h)(g)(f));
console.log(m(15)); // -> "number 3"
Applying a layer of abstraction, you can think of the o
and p
functions as
having nodes within the compositional stack. Each of these nodes is denoted in
a seperate set of brackets.
c(o(node1)(node2)(node3));
When necessary, type annotations can be placed between nodes. For o
, each
annotation somewhat counterintuitively represents the input type of the
following node, and the second annotation of the first node represents the final
result.
const m = c(o<string, string>(f)<number>(g)<number>(h)<number>(l));
For p
, each annotation represents the output type of the following node, and
the second annotation of the first node represents the final input.
const m = c(p<number, number>(l)<number>(h)<string>(g)<string>(f));
Generated API documentation, with pseudohaskell illustrations of type signatures, is available here.
Your IDE will probably show a disgusting, monstrosity of a type signature. That's because this project uses recursive types in order to provide robust type safety. Type checking is still completely functional.