Composable Datatypes with Functions

Smoke Art Cubes to Smoke — MattysFlicks — (CC BY 2.0)
const t = value => {
  const fn = () => value;  fn.toString = () => `t(${ value })`;  return fn;
};
const someValue = t(2);console.log(
  someValue.toString() // "t(2)"
);
  • t(x)(t(1)) ==== t(x + 1)
  • t(x)(t(1)).toString() === t(x + 1).toString()
const assert = {
  same: (actual, expected, msg) => {
    if (actual.toString() !== expected.toString()) {
      throw new Error(`NOT OK: ${ msg }
        Expected: ${ expected }
        Actual:   ${ actual }
      `);
    }    console.log(`OK: ${ msg }`);
  }
};
{
  const msg = 'a value t(x) composed with t(0) ==== t(x)';
  const x = 20;
  const a = t(x)(t(0));
  const b = t(x);
  assert.same(a, b, msg);
}{
  const msg = 'a value t(x) composed with t(1) ==== t(x + 1)';
  const x = 20;
  const a = t(x)(t(1));
  const b = t(x + 1);
  assert.same(a, b, msg);
}
NOT OK: a value t(x) composed with t(0) ==== t(x)
        Expected: t(20)
        Actual:   20
  1. Add a .valueOf() method to the t type so that the new add() function can take instances of t() as arguments. The + operator will use the result of n.valueOf() as the second operand.
  2. Assign the methods to the add() function with Object.assign().
const t = value => {
  const add = n => t(value + n);  return Object.assign(add, {
    toString: () => `t(${ value })`,
    valueOf: () => value
  });
};
"OK: a value t(x) composed with t(0) ==== t(x)"
"OK: a value t(x) composed with t(1) ==== t(x + 1)"
// Compose functions from top to bottom:
const pipe = (...fns) => x => fns.reduce((y, f) => f(y), x);// Sugar to kick off the pipeline with an initial value:
const sumT = (...fns) => pipe(...fns)(t(0));sumT(
  t(2),
  t(4),
  t(-1)
).valueOf(); // 5

You Can Do This with Any Data Type

It doesn’t matter what shape your data takes, as long as there is some composition operation that makes sense. For lists or strings, it could be concatenation. For DSP, it could be signal summing. Of course lots of different operations might make sense for the same data. The question is, which operation best represents the concept of composition? In other words, which operation would benefit most expressed like this?:

const result = compose(
  value1,
  value2,
  value3
);

Composable Currency

Moneysafe is an open source library that implements this style of composable functional datatypes. JavaScript’s Number type can't accurately represent certain fractions of dollars.

.1 + .2 === .3 // false
npm install --save moneysafe
import { $ } from 'moneysafe';$(.1) + $(.2) === $(.3).cents; // true
import { $ } from 'moneysafe';
import { $$, subtractPercent, addPercent } from 'moneysafe/ledger';$$(
  $(40),
  $(60),
  // subtract discount
  subtractPercent(20),
  // add tax
  addPercent(10)
).$; // 88

Test Your Understanding

Clone Moneysafe:

git clone git@github.com:ericelliott/moneysafe.git
npm install
npm run watch
rm source/moneysafe.js && touch source/moneysafe.js

Next Steps

Want to learn more about function composition with JavaScript?


JavaScript Scene

JavaScript, software leadership, software development, and related technologies.

Eric Elliott

Written by

Make some magic. #JavaScript

JavaScript Scene

JavaScript, software leadership, software development, and related technologies.