I'd like to take a look at some functions whose form and function are the epitome of elegant.
The B combinator, sometimes called "compose":
const B = (f) => (g) => (x) => f(g(x))
Which has the type:
(b -> c) → (a -> b) → a → c
in TypeScript:
const B = <A, B, C>(g: (y: B) => C) =>
(f: (x: A) => B) =>
(a: A): C =>
g(f(a));
What it does is combine two unary functions (single-argument functions) together such that the output of the second is the input of the first. This is the core of composing functions in math as well as programming. If you have a couple procedures you want to link and sequence consider using this operator to do so.
const armTheMissiles = (missles: Missle[]): Missle[] => {...}
const fireTheMissles = (missles: Missle[]): void => {...}
const armAndFireMissles = B(fireTheMissles)(armTheMissles)
This is such a core way of writing code that advanced languages like Haskell and F# have operators dedicated to it: .
and <<
, respectively.
armAndFireMissles = fireTheMissles . armTheMissles
An easy place to integrate this function into your code is in cases where you take a callback that calls a function with it's parameters.
const fetchStuff = () =>
fetch(...).then(data => parseData(validate(data)))
In this case you can use the B combinator to drop the inner lambda:
const fetchStuff = () =>
fetch(...).then(B(parseData)(validate))
This way of eliminating lambdas by using composition techniques is called eta-reduction.
It may help to have an idea of what a partially applied B
means. I like to think of it as the function has its arms out and is ready for the conga line.
const congaReadyFoo = B(foo);
const congaReadyBar = B(bar);
const congaLine = congaReadyFoo(congaReadyBar(baz));
// where foo, bar, and baz are all unary functions with compatible inputs and outputs.
That all said, it's easy to go too far with this kind of technique.
// probably too far
const congaLine = B(foo)(B(bar)(baz))
This is mainly due to the syntax as when it's an operator it's much easier to follow (haskell):
congaLine = foo . bar . baz
There's many more fun combinators but I wanted to start with one many people may already be familiar with from high school math.
Bonus fact: B
turns functions themselves into a functor
class Functor f where
fmap :: (a -> b) -> f a -> f b
instance Functor ((->) r) where
fmap = (.)
That is, given a function from a -> b
, our compose function takes a function which takes something of type a
and returns a function which takes something of type b
. This means it's totally cool to think of composition as mapping a function over another function.
Top comments (2)
Very good explanation!
I never really understood or even cared about the concept until I saw it used in production (all those wasted years!). If you have time, maybe add a real-world practical use example for stragglers like me 😉
I showed one which is
to