The Symmetry of JavaScript Functions (revised)

In JavaScript, functions are first-class entities: You can store them in data structures, pass them to other functions, and return them from functions. An amazing number of very strong programming techniques arise as a consequence of functions-as-first…


This content originally appeared on raganwald.com and was authored by Reginald Braithwaite

In JavaScript, functions are first-class entities: You can store them in data structures, pass them to other functions, and return them from functions. An amazing number of very strong programming techniques arise as a consequence of functions-as-first-class-entities. One of the strongest is also one of the simplest: You can write functions that compose and transform other functions.

a very, very, very basic introduction to decorators

Let’s consider logical negation. We might have a function like this:

const isaFruit = (f) =>
  f === 'apple' || f === 'banana' || f === 'orange';

isaFruit('orange')
  //=> true

We can use it to pick fruit from a basket, using an array’s .filter method:

['pecan',
 'apple',
 'chocolate',
 'cracker',
 'orange'].filter(isaFruit)
  //=> ["apple","orange"]

What if we want the things-that-are-not-fruit? There are a few solutions. Languages like Smalltalk and Ruby have a style where collections provide a .reject method. Or we could write a notaFruit function:

const notaFruit = (f) =>
  f !== 'apple' && f !== 'banana' && f !== 'orange';

['pecan',
 'apple',
 'chocolate',
 'cracker',
 'orange'].filter(notaFruit)
  //=> ["pecan","chocolate","cracker"]

We could also take advantage of function expressions and inline the logical negation of isaFruit:

['pecan',
 'apple',
 'chocolate',
 'cracker',
 'orange'].filter(it => !isaFruit(it));
  //=> ["pecan","chocolate","cracker"]

That is interesting. It’s a pattern we can repeat to find things in the basket that don’t start with “c:”

const startsWithC = (f) =>
  f[0] === 'c' || f[0] === 'C';

['pecan',
 'apple',
 'chocolate',
 'cracker',
 'orange'].filter(it => !startsWithC(it))
  //=> ["pecan","apple","orange"]

We can take advantage of functions-as-first-class-entities to turn this pattern into a function that modifies another function. We can use that to name another function, or even use it inline as an expression:

const not = (fn) =>
  (...args) =>
    !(fn(...args))

const anotherNotaFruit = not(isaFruit);

anotherNotaFruit("pecan")
  //=> true

['pecan',
 'apple',
 'chocolate',
 'cracker',
 'orange'].filter(not(startsWithC))
  //=> ["pecan","apple","orange"]

not is a decorator, a function that takes another function and “decorates it” with new functionality that is semantically related to the original function’s behaviour. This allows us to use not(isaFruit) anywhere we could use isaFuit, or use not(startsWithC) anywhere we can use startsWithC.

not is so trivial that it doesn’t feel like it wins us much, but the exact same principle allows us to write decorators like maybe:

const maybe = (fn) =>
  (...args) => {
    for (let arg of args) {
      if (arg == null) return arg;
    }
    return fn(...args);
  }

[1, null, 3, 4, null, 6, 7].map(maybe(x => x * x))
  //=> [1,null,9,16,null,36,49]

And to make combinators like compose:

const compose = (fn, ...rest) =>
  rest.length === 0
  ? fn
  : (arg) => fn(compose(...rest)(arg));

compose(x => x + 1, y => y * y)(10)
  //=> 101

You’ll find lots of other decorators and combinators swanning about in books about using functions in JavaScript. And your favourite JavaScript library is probably loaded with decorators that memoize the result of an idempotent function, or debounce functions that you may use to call a server from a browser.

what makes decorators and combinators easy

The power arising from functions-as-first-class-entities is that we have a very flexible way to make functions out of functions, using functions. We are not “multiplying our entities unnecessarily.” On the surface, decorators and combinators are made possible by the fact that we can pass functions to functions, and return functions that invoke our original functions.

But there’s something else: The fact that all functions are called in the exact same way. We write foo(bar) and know that we will evaluate bar, and pass the resulting value to the function we get by evaluating foo. This allows us to write decorators and combinators that work with any function.

Or does it?

what would make decorators and combinators difficult

Imagine, if you will, that functions came in two colours: blue, and khaki. Now imagine that when we invoke a function in a variable, we type the name of the function in the proper colour. So if we write const square = (x) => x * x, we also have to write square(5), so that square is always blue.

If we write const square = (x) => x * x, but elsewhere we write square(5), it won’t work because square is a blue function and square(5) would be a khaki invocation.

If functions worked like that, decorators would be very messy. We’d have to make colour-coded decorators, like a blue maybe and a khaki maybe. We’d have to carefully track which functions have which colours, much as in gendered languages like French, you need to know the gender of all inanimate objects so that you can use the correct gendered grammar when talking about them.

This sounds bad, and for programming tools, it is.1 The general principle is: Have fewer kinds of similar things, but allow the things you do have to combine in flexible ways. You can’t just remove things, you have to also make it very easy to combine things. Functions as first-class-entities are a good example of this, because they allow you to combine functions in flexible ways.

Coloured functions would be an example of how not to do it, because you’d be making it harder to combine functions by balkanizing them.2

Functions don’t have colours in JavaScript. But there are things that have this kind of asymmetry that make things just as awkward. For example, methods in JavaScript are functions. But, when you invoke them, you have to get this set up correctly. You have to either:

  1. Invoke a method as a property of an object. e.g. foo.bar(baz) or foo['bar'](baz).
  2. Bind an object to a method before invoking it, e.g. bar.bind(foo).
  3. Invoke the method with with .call or .apply, e.g bar.call(foo, baz).

Thus, we can imagine that calling a function directly (e.g. bar(baz)) is blue, invoking a function and setting this (e.g. bar.call(foo, baz)) is khaki.

Or in other words, functions are blue, and methods are khaki.

the composability problem

We often write decorators in blue, a/k/a pure functional style. Here’s a decorator that makes a function throw an exception if its argument is not a finite number:

const requiresFinite = (fn) =>
  (n) => {
    if (Number.isFinite(n)){
      return fn(n);
    }
    else throw "Bad Wolf";
  }

const plusOne = x => x + 1;

plusOne(1)
  //=> 2

plusOne([])
  //=> 1 WTF!?

const safePlusOne = requiresFinite(plusOne);

safePlusOne(1)
  //=> 2

safePlusOne([])
  //=> throws "Bad Wolf"

But it won’t work on methods. Here’s a Circle class that has an unsafe .scaleBy method:

class Circle {
  constructor (radius) {
    this.radius = radius;
  }
  circumference () {
    return Math.PI * 2 * this.radius;
  }
  scaleBy (factor) {
    return new Circle(factor * this.radius);
  }
}

const two = new Circle(2);

two.scaleBy(3).circumference()
  //=> 37.69911184307752

two.scaleBy(null).circumference()
  //=> 0 WTF!?

Let’s decorate the scaleBy method to check its argument:

Circle.prototype.scaleBy = requiresFinite(Circle.prototype.scaleBy);

two.scaleBy(null).circumference()
  //=> throws "Bad Wolf"

Looks good, let’s put it into production:

Circle.prototype.scaleBy = requiresFinite(Circle.prototype.scaleBy);

two.scaleBy(3).circumference()
  //=> undefined is not an object (evaluating 'this.radius')

Whoops, we forgot that method invocation is khaki code, so our blue requiresFinite decorator will not work on methods. This is the problem of khaki and blue code colliding.

composing functions with green code

Fortunately, we can write higher-order functions like decorators and combinators in a style that works for both “pure” functions and for methods. We have to use the function keyword so that this is bound, and then invoke our decorated function using .call so that we can pass this along.

Here’s requiresFinite written in this style, which we will call green . It works for decorating both methods and functions:

const requiresFinite = (fn) =>
  function (n) {
    if (Number.isFinite(n)){
      return fn.call(this, n);
    }
    else throw "Bad Wolf";
  }

Circle.prototype.scaleBy = requiresFinite(Circle.prototype.scaleBy);

two.scaleBy(3).circumference()
  //=> 37.69911184307752

two.scaleBy("three").circumference()
  //=> throws "Bad Wolf"

const safePlusOne = requiresFinite(x => x + 1);

safePlusOne(1)
  //=> 2

safePlusOne([])
  //=> throws "Bad Wolf"

We can write all of our decorators and combinators in green style. For example, instead of writing maybe in functional (blue) style like this:

const maybe = (fn) =>
  (...args) => {
    for (let arg of args) {
      if (arg == null) return arg;
    }
    return fn(...args);
  }

We can write it in both functional and method style ( green ) style like this:

const maybe = (method) =>
  function (...args) {
    for (let arg of args) {
      if (arg == null) return arg;
    }
    return method.apply(this, args);
  }

And instead of writing our simple compose in functional (blue) style like this:

const compose = (a, b) =>
  (x) => a(b(x));

We can write it in both functional and method style ( green ) style like this:

const compose = (a, b) =>
  function (x) {
    return a.call(this, b.call(this, x));
  }

What makes JavaScript tolerable is that green handling works for both functional (blue) and method invocation (khaki) code. But when writing large code bases, we have to remain aware that some functions are blue and some are khaki, because if we write a mostly blue program, we could be lured into complacency with with blue decorators and combinators for years. But everything would break if a khaki method was introduced that didn’t play nicely with our blue combinators

The safe thing to do is to write all our higher-order functions in green style, so that they work for functions or methods. And that’s why we might talk about the simpler, blue form when introducing an idea, but we write out the more complete, green form when implementing it as a recipe.

red functions vs. object factories

JavaScript classes (and the equivalent prototype-based patterns) rely on creating objects with the new keyword. As we saw in the example above:

class Circle {
  constructor (radius) {
    this.radius = radius;
  }
  circumference () {
    return Math.PI * 2 * this.radius;
  }
  scaleBy (factor) {
    return new Circle(factor * this.radius);
  }
}

const round = new Circle(1);

round.circumference()
  //=> 6.2831853

That new keyword introduces yet another colour of function, constructors are red functions. We can’t make circles using blue function calls:

const round2 = Circle(2);
  //=> Cannot call a class as a function

[1, 2, 3, 4, 5].map(Circle)
  //=> Cannot call a class as a function

And we certainly can’t use a decorator on them:

const CircleRequiringFiniteRadius = requiresFinite(Circle);

const round3 = new CircleRequiringFiniteRadius(3);
  //=> Cannot call a class as a function

Some experienced developers dislike new because of this problem: It introduces one more kind of function that doesn’t compose neatly with other functions using our existing decorators and combinators.

We could eliminate red functions by using prototypes and Object.create instead of using the class and new keywords. A “factory function” is a function that makes new objects. So instead of writing a Circle class, we would write a CirclePrototype and a CircleFactory function:

const CirclePrototype = {
  circumference () {
    return Math.PI * 2 * this.radius;
  },
  scaleBy (factor) {
    return CircleFactory(factor * this.radius);
  }
};

const CircleFactory = (radius) =>
  Object.create(CirclePrototype, {
    radius: { value: radius, enumerable: true }
  })

CircleFactory(2).scaleBy(3).circumference()
  //=> 37.69911184307752

Now we have a blue CircleFactory function, and we have the benefits of objects and methods, along with the benefits of decorating and composing factories like any other function. For example:

const requiresFinite = (fn) =>
  function (n) {
    if (Number.isFinite(n)){
      return fn.call(this, n);
    }
    else throw "Bad Wolf";
  }

const FiniteCircleFactory = requiresFinite(CircleFactory);

FiniteCircleFactory(2).scaleBy(3).circumference()
  //=> 37.69911184307752

FiniteCircleFactory(null).scaleBy(3).circumference()
  //=> throws "Bad Wolf"

All that being said, programming with factory functions instead of with classes and new is not a cure-all. Besides losing some of the convenience and familiarity for other developers, we’d also have to use extreme discipline for fear that accidentally introducing some red classes would break our carefully crafted “blue in green” application.

In the end, there’s no avoiding the need to know which functions are functions, and which are actually classes. Tooling can help: Some linting applications can enforce a naming convention where classes start with an upper-case letter and functions start with a lower-case letter.

charmed functions

Consider:

const likesToDrink = (whom) => {
  switch (whom) {
    case 'Bob':
      return 'Ristretto';
    case 'Carol':
      return 'Cappuccino';
    case 'Ted':
      return 'Allongé';
    case 'Alice':
      return 'Cappuccino';
  }
}

likesToDrink('Alice')
  //=> 'Cappuccino'

likesToDrink('Peter')
  //=> undefined;

That’s a pretty straightforward function that implements a mapping from Bob, Carol, Ted, and Alice to the drinks ‘Ristretto’, ‘Cappuccino’, and ‘Allongé’. The mapping is encoded implicitly in the code’s switch statement.

We can use it in combination with other functions. For example, we can find out if the first letter of what someone likes is “c:”

const startsWithC = (something) => !!something.match(/^c/i)

startsWithC(likesToDrink('Alice'))
  //=> true

const likesSomethingStartingWithC =
  compose(startsWithC, likesToDrink);

likesSomethingStartingWithC('Ted')
  //=> false

So far, that’s good, clean blue function work. But there’s yet another kind of “function call.” If you are a mathematician, this is a mapping too:

const personToDrink = {
  Bob: 'Ristretto',
  Carol: 'Cappuccino',
  Ted: 'Allongé',
  Alice: 'Cappuccino'
}

personToDrink['Alice']
  //=> 'Cappuccino'

personToDrink['Ted']
  //=> 'Allongé'

personToDrink also maps the names ‘Bob’, ‘Carol’, ‘Ted’, and ‘Alice’ to the drinks ‘Ristretto’, ‘Cappuccino’, and ‘Allongé’, just like likesToDrink. But even though it does the same thing as a function, we can’t use it as a function:

const personMapsToSomethingStartingWithC =
  compose(startsWithC, personToDrink);

personMapsToSomethingStartingWithC('Ted')
  //=> undefined is not a function (evaluating 'b.call(this, x)')

As you can see, [ and ] are a little like ( and ), because we can pass Alice to personToDrink and get back Cappuccino. But they are just different enough, that we can’t write personToDrink(...). Objects (as well as ES-6 maps and sets) are “charmed functions.”

And you need a different piece of code to go with them. We’d need to write things like this:

const composeblueWithCharm =
  (bluefunction, charmedfunction) =>
    (arg) =>
      bluefunction(charmedfunction[arg]);

const composeCharmWithblue =
  (charmedfunction, bluefunction) =>
    (arg) =>
      charmedfunction[bluefunction(arg)]

// ...

That would get really old, really fast.

adapting to handle red and charmed functions

We can work our way around some of these cross-colour and charm issues by writing adaptors, wrappers that turn red and charmed functions into blue functions. As we saw above, a “factory function” is a function that is called in the normal way, and returns a freshly created object.

If we wanted to create a CircleFactory, we could use Object.create as we saw above. We could also wrap new Circle(...) in a function:

class Circle {
  constructor (radius) {
    this.radius = radius;
  }
  circumference () {
    return Math.PI * 2 * this.radius;
  }
  scaleBy (factor) {
    return new Circle(factor * this.radius);
  }
}

const CircleFactory = (radius) =>
  new Circle(radius);

CircleFactory(2).scaleBy(3).circumference()
  //=> 37.69911184307752

With some argument jiggery-pokery, we could abstract Circle from CircleFactory and make a factory for making factories, a FactoryFactory:

We would write a CircleFactory function:

const FactoryFactory = (clazz) =>
  (...args) =>
    new clazz(...args);

const CircleFactory = FactoryFactory(Circle);

circleFactory(5).circumference()
  //=> 31.4159265

FactoryFactory turns any red class into a blue function. So we can use it any where we like:

[1, 2, 3, 4, 5].map(FactoryFactory(Circle))
  //=>
    [{"radius":1},{"radius":2},{"radius":3},{"radius":4},{"radius":5}]

Sadly, we still have to remember that Circle is a class and be sure to wrap it in FactoryFactory when we need to use it as a function, but that does work.

We can do a similar thing with our “charmed” maps (and arrays, for that matter). Here’s Dictionary, a function that turns objects and arrays (our “charmed” functions) into ordinary (blue) functions:

const Dictionary = (data) => (key) => data[key];

const personToDrink = {
  Bob: 'Ristretto',
  Carol: 'Cappuccino',
  Ted: 'Allongé',
  Alice: 'Cappuccino'
}

['Bob', 'Ted', 'Carol', 'Alice'].map(Dictionary(personToDrink))
  //=> ["Ristretto","Allongé","Cappuccino","Cappuccino"]

Dictionary makes it easier for us to use all of the same tools for combining and manipulating functions on arrays and objects that we do with functions.

dictionaries as proxies

As David Nolen has pointed out, languages like Clojure have maps that can be called as functions automatically. This is superior to wrapping a map in a plain function, because the underlying map is still available to be iterated over and otherwise treated as a map. Once we wrap a map in a function, it becomes opaque, useless for anything except calling as a function.

If we wish, we can create a dictionary function that is a partial proxy for the underlying collection object. For example, here is an IterableDictionary that turns a collection into a function that is also iterable if its underlying data object is iterable:

const IterableDictionary = (data) => {
  const proxy = (key) => data[key];
  proxy[Symbol.iterator] = function* (...args) {
    yield* data[Symbol.iterator](...args);
  }
  return proxy;
}

const people = IterableDictionary(['Bob', 'Ted', 'Carol', 'Alice']);
const drinks = IterableDictionary(personToDrink);

for (let name of people) {
  console.log(`${name} prefers to drink ${drinks(name)}`)
}
  //=>
    Bob prefers to drink Ristretto
    Ted prefers to drink Allongé
    Carol prefers to drink Cappuccino
    Alice prefers to drink Cappuccino

This technique has limitations. For example, objects in JavaScript are not iterable by default. So we can’t write:

for (let [name, drink] of personToDrink) {
  console.log(`${name} prefers to drink ${drink}`)
}
  //=> undefined is not a function (evaluating 'personToDrink[Symbol.iterator]()')

We could write:

for (let [name, drink] of Object.entries(personToDrink)) {
  console.log(`${name} prefers to drink ${drink}`)
}
  //=>
    Bob prefers to drink Ristretto
    Carol prefers to drink Cappuccino
    Ted prefers to drink Allongé
    Alice prefers to drink Cappuccino

It would be an enormous hack to make Object.entries(IterableDictionary(personToDrink)) work. While we’re at it, how would we make .length work? Functions implement .length as the number of arguments they accept. Arrays implement it as the number of entries they hold. If we wrap an array in a dictionary, what is its .length?

Proxying collections, meaning “creating an object that behaves like the collection,” works for specific and limited contexts, but it is enormously fragile to attempt to make a universal proxy that also acts as a function.

summary

JavaScript’s elegance comes from having a simple thing, functions, that can be combined in many flexible ways. Exceptions to the ways functions combine, like the new keyword, handling this, and [...], make combining awkward, but we can work around that by writing adaptors to convert these exceptions to regular function calls.

p.s. For bonus credit, write adaptors for EcmaScript’s Map and Set collections.

p.p.s. Some of this material was originally published in Reusable Abstractions in CoffeeScript (2012). If you’re interested in Ruby, Paul Mucur wrote a great post about Data Structures as Functions.


edit this page

This post was extracted from a draft of the book, JavaScript Allongé, The “Six” Edition. The extracts so far:

  1. Bad for programming languages, of course. French is a lovely human language. 

  2. Bob Nystrom introduced this excellent metaphor in What colour is your function? 


This content originally appeared on raganwald.com and was authored by Reginald Braithwaite


Print Share Comment Cite Upload Translate Updates
APA

Reginald Braithwaite | Sciencx (2015-03-12T00:00:00+00:00) The Symmetry of JavaScript Functions (revised). Retrieved from https://www.scien.cx/2015/03/12/the-symmetry-of-javascript-functions-revised/

MLA
" » The Symmetry of JavaScript Functions (revised)." Reginald Braithwaite | Sciencx - Thursday March 12, 2015, https://www.scien.cx/2015/03/12/the-symmetry-of-javascript-functions-revised/
HARVARD
Reginald Braithwaite | Sciencx Thursday March 12, 2015 » The Symmetry of JavaScript Functions (revised)., viewed ,<https://www.scien.cx/2015/03/12/the-symmetry-of-javascript-functions-revised/>
VANCOUVER
Reginald Braithwaite | Sciencx - » The Symmetry of JavaScript Functions (revised). [Internet]. [Accessed ]. Available from: https://www.scien.cx/2015/03/12/the-symmetry-of-javascript-functions-revised/
CHICAGO
" » The Symmetry of JavaScript Functions (revised)." Reginald Braithwaite | Sciencx - Accessed . https://www.scien.cx/2015/03/12/the-symmetry-of-javascript-functions-revised/
IEEE
" » The Symmetry of JavaScript Functions (revised)." Reginald Braithwaite | Sciencx [Online]. Available: https://www.scien.cx/2015/03/12/the-symmetry-of-javascript-functions-revised/. [Accessed: ]
rf:citation
» The Symmetry of JavaScript Functions (revised) | Reginald Braithwaite | Sciencx | https://www.scien.cx/2015/03/12/the-symmetry-of-javascript-functions-revised/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.