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-class-entities. One of the strongest is also one of the simplest: You can write functions that compose and transform other functions.

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:

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:

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:

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 bluemaybe and a khakimaybe. 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:

Invoke a method as a property of an object. e.g. foo.bar(baz) or foo['bar'](baz).

Bind an object to a method before invoking it, e.g. bar.bind(foo).

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:

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 bluerequiresFinite 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:

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

constcompose=(a,b)=>(x)=>a(b(x));

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

constcompose=(a,b)=>function(x){returna.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:

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

constround2=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:

constCircleRequiringFiniteRadius=requiresFinite(Circle);constround3=newCircleRequiringFiniteRadius(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:

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.

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:”

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:

constpersonMapsToSomethingStartingWithC=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:

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:

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:

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.