JavaScript Values Algebra

One of JavaScript’s defining characteristics is its treatment of functions as first-class values. Like numbers, strings, and other kinds of objects, references to functions can be passed as arguments to functions, returned as the result from functions, bound to variables, and generally treated like any other value.1

Here’s an example of passing a reference to a function around. This simple array-backed stack has an undo function. It works by creating a function representing the action of undoing the last update, and then pushing that onto a stack of actions to be undone:

Functions-as-values is a powerful idea. And people often look at the idea of functions-as-values and think, “Oh, JavaScript is a functional programming language.” No.

In computer science, functional programming is a programming paradigm, a style of building the structure and elements of computer programs, that treats computation as the evaluation of mathematical functions and avoids state and mutable data.–Wikipedia

Functional programming might have meant “functions as first-class values” in the 1960s when Lisp was young. But time marches on, and we must march alongside it. JavaScript does not avoid state, and JavaScript embraces mutable data, so JavaScript does not value “functional programming.”

objects

JavaScript’s other characteristic is its support for objects. Although JavaScript’s features seem paltry compared to rich OO languages like Scala, its extreme minimalism means that you can actually build almost any OO paradigm up from basic pieces.

Now, people often hear the word “objects” and think kingdom of nouns. But objects are not necessarily nouns, or at least, not models for obvious, tangible entities in the real world.

One example concerns state machines. We could implement a cell in Conway’s Game of Life using if statements and a boolean property to determine whether the cell was alive or dead:2

You could say that the “state” of the cell is represented by the primitive value 'alive' for alive, or 'dead' for dead. But that isn’t modeling the state in any way, that’s just a name. The true state of the object is implicit in the object’s behaviour, not explicit in the value of the .state property.

In this design, delegateToOwn delegates the methods .alive and .stateInNextGeneration to whatever object is the value of a Cell’s state property.

So when we write someCell.state = Alive, then the Alive object will handle someCell.alive and someCell.aliveInNextGeneration. And when we write someCell.state = Dead, then the Dead object will handle someCell.alive and someCell.aliveInNextGeneration.

Now we’ve taken the implicit states of being alive or dead and transformed them into the first-class values Alive and Dead. Not a string that is used implicitly in some other code, but all of “The stuff that matters about aliveness and deadness.”

This is not different than the example of passing functions around: They’re both the same thing, taking something would be implicit in another design and/or another language, and making it explicit, making it a value. And making the whole thing a value, not just a boolean or a string, the complete entity.

This example is the same thing as the example of a stack that handles undo with a stack of functions: Behaviour is treated as a first-class value, whether it be a single function or an object with multiple methods.

an algebra of functions

If we left it at that, we could come away with an idea that a function is a small, single-purposed object. We would be forgiven for thinking that there’s nothing special about functions, they’re values representing behaviour.

But functions have another, very important purpose. The form the basis for an algebra of values.

Consider these functions, begin1 and begin. They’re handy for writing function advice, for creating sequences of functions to be evaluated for side effects, and for resolving method conflicts when composing mixins:

Both begin1 and begin take one or more functions, and turn them into a third function. This is much the same as + taking two numbers, and turning them into a third number.3

When you have a bunch of functions that do things in your problem domain (like writeLedger and withdrawFunds), and you have a way to compose your domain functions, you have a little algebra for taking values and computing new values from them.

Just as we can write 1 + 1 = 2, we can also write writeLedger + withdrawFunds = transaction. We have created a very small algebra of functions. We can write functions that transform functions into other functions.

an algebra of values

Functions that transform functions into other functions are very powerful, but it does not stop there.

It’s obvious that functions can take objects as arguments and return objects. Functions (or methods) that take an object representing a client and return an object representing and account balance are a necessary and important part of software.

But just as we created an algebra of functions, we can write an algebra of objects. Meaning, we can write functions that take objects and return other objects that represent a transformation of their arguments.

Here’s a function that transforms an object into a proxy for that object:

The proxy function transforms an object into another object with a similar purpose. Functions can compose objects as well, here’s one of the simplest examples:

var__slice=[].slice;functionmeld(){varmelded={},providers=__slice.call(arguments,0),key,i,provider,except;for(i=0;i<providers.length;++i){provider=providers[i];for(keyinprovider){if(provider.hasOwnProperty(key)){melded[key]=provider[key];};};};returnmelded;};varPerson={fullName:function(){returnthis.firstName+" "+this.lastName;},rename:function(first,last){this.firstName=first;this.lastName=last;returnthis;}};varHasCareer={career:function(){returnthis.chosenCareer;},setCareer:function(career){this.chosenCareer=career;returnthis;},describe:function(){returnthis.fullName()+" is a "+this.chosenCareer;}};varPersonWithCareer=meld(Person,HasCareer);//=>{fullName:[Function],rename:[Function],career:[Function],setCareer:[Function],describe:[Function]}

Functions that transform objects or compose objects act at a higher level than functions that query objects or update objects. They form an algebra that allows us to build objects by transformation and composition, just as we can use functions like begin to build functions by composition.

javascript values algebra

JavaScript treats functions and objects as first-class values. And the power arising from this is the ability to write functions that transform and compose first-class values, creating an algebra of values.

This applies to transforming and composing functions, and it also applies to transforming and composing objects.

appendix 1: a function for composing prototypes out of mixins

First, some helper functions:

var__slice=[].slice;// extendfunctionextend(){varconsumer=arguments[0],providers=__slice.call(arguments,1),key,i,provider;for(i=0;i<providers.length;++i){provider=providers[i];for(keyinprovider){if(provider.hasOwnProperty(key)){consumer[key]=provider[key];};};};returnconsumer;};// partialProxy is like "proxy," but it proxies a subset of an// object's methods and it also has a fixed set of mutable propertiesfunctionpartialProxy(baseObject,methods,mutableProperties){varproxyObject=Object.create(null);if(mutableProperties){mutableProperties.forEach(function(privatePropertyName){proxyObject[privatePropertyName]=null;});}methods.forEach(function(methodName){proxyObject[methodName]=function(){varresult=baseObject[methodName].apply(baseObject,arguments);return(result===baseObject)?proxyObject:result;}});Object.preventExtensions(proxyObject);returnproxyObject;}// extendWith Proxy extends an object with behaviour, but restricts// the behaviour to interact with a proxy to the object. This// encapsulates each set of behaviour from the object and from each// other, reducing coupling.varnumber=0;functionmethodsOfType(behaviour,type){varmethods=[],methodName;for(methodNameinbehaviour){if(typeof(behaviour[methodName])===type){methods.push(methodName);}};returnmethods;}functionpropertyFlags(behaviour){varproperties=[],propertyName;for(propertyNameinbehaviour){if(behaviour[propertyName]===null){properties.push(propertyName);}}returnproperties;}functionextendWithProxy(baseObject,behaviour){varsafekeepingName="__"+++number+"__",definedMethods=methodsOfType(behaviour,'function'),dependencies=methodsOfType(behaviour,'undefined'),properties=propertyFlags(behaviour),methodName;definedMethods.forEach(function(methodName){baseObject[methodName]=function(){varcontext=this[safekeepingName],result;if(context==null){context=partialProxy(this,definedMethods.concat(dependencies),properties);properties.forEach(function(propertyName){context[propertyName]=behaviour[propertyName];});Object.defineProperty(this,safekeepingName,{enumerable:false,writable:false,value:context});}result=behaviour[methodName].apply(context,arguments);return(result===context)?this:result;};});returnbaseObject;}

The Prototype function builds prototypes out of an optional super-prototype (or null) and one or more behaviours, objects with functions to mix in.

The Prototype function above can mix more than one behaviour into a prototype, but sometimes you want to make a new behaviour out of two or more existing behaviours without turning them into a prototype. composeBehaviour does that.

It is involved because it must check for conflicts and resolve them at the time of composition. The Prototype method above is simpler because the individual behaviours each get their own proxy with private state. composeBehaviour wires behaviours up so they can share a proxy.

functioncomposeBehaviour(){varmixins=__slice.call(arguments,0),dummy=function(){};returnmixins.reduce(function(acc1,mixin){returnObject.keys(mixin).reduce(function(result,methodName){varbDefinition=mixin[methodName],bType=typeof(bDefinition),aDefinition,aType,bResolverKey,bDefinition;if(result.hasOwnProperty(methodName)){aDefinition=result[methodName];aType=typeof(aDefinition);if(aDefinition===null&&bDefinition===null){throw"'"+methodName+"' cannot be private to multiple mixins."}elseif(aDefinition===null||bDefinition===null){throw"'"+methodName+"' cannot be a method and a property."}elseif(aType==='undefined'){if(bType==='function'||bType==='undefined'){result[methodName]=bDefinition;}elseif(bType==='object'){bResolverKey=Object.keys(bDefinition)[0];bDefinition=bDefinition[bResolverKey];if(bResolverKey==='around'){result[methodName]=function(){returnbDefinition.apply(this,[dummy]+__slice.call(0,arguments));}}elseresult[methodName]=bDefinition;}elsethrowaType+" cannot be mixed in as '"+methodName+"'";}elseif(bType==='object'){bResolverKey=Object.keys(bDefinition)[0];bDefinition=bDefinition[bResolverKey];result[methodName]=policies[bResolverKey](aDefinition,bDefinition);}elseif(bType==='undefined'){// do nothing}elsethrow"unresolved method conflict for '"+methodName+"'";}elseif(bDefinition===null){result[methodName]=null;}elseif(bType==='function'||bType==='undefined'){result[methodName]=bDefinition;}elseif(bType==='object'){bResolverKey=Object.keys(bDefinition)[0];bDefinition=bDefinition[bResolverKey];if(bResolverKey==='around'){result[methodName]=function(){returnbDefinition.apply(this,[dummy]+__slice.call(0,arguments));}}elseresult[methodName]=bDefinition;}elsebType+" cannot be used for '"+methodName+"'";returnresult;},acc1);},{});}

notes

JavaScript does not actually pass functions or any other kind of object around, it passes references to functions and other objects around. But it’s awkward to describe var I = function (a) { return a; } as binding a reference to a function to the variable I, so we often take an intellectually lazy shortcut, and say the function is bound to the variable I. This is much the same shorthand as saying that somebody added me to the schedule for an upcoming conference: They really added my name to the list, which is a kind of reference to me. ↩