Methods - JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

JavaScript Spessore: A thick shot of objects, metaobjects, & protocols (2014)

Methods

20

In This Chapter

In this chapter, we’ll look at methods in more detail, and explore metamethods.

What is a Method?

information

In object-oriented programming, a method (or member function) is a subroutine (or procedure or function) associated with an object, and which has access to its data, its member variables.

Wikipedia

As an abstraction, an object is an independent entity that maintains internal state and that responds to messages by reporting its internal state and/or making changes to its internal state. In the course of handling a message, an object may send messages to other objects and receive replies from them.

A “method” is an another idea that is related to, but not the same as, handling a message. A method is a function that encapsulates an object’s behaviour. Methods are invoked by a calling entity much as a function is invoked by some code.

The distinction may seem subtle, but the easiest way to grasp the distinction is to focus on the word “message.” A message is an entity of its own. You can store and forward a message. You can modify it. You can copy it. You can dispatch it to multiple objects. You can put it on a “blackboard” and allow objects to decide for themselves whether they want to respond to it.

Methods, on the other hand, operate “closer to the metal.” They look and behave like function calls. In JavaScript, methods are functions. To be a method, a function must be the property of an object. We’ve seen methods earlier, here’s a naïve example:

var dictionary = {

abstraction: "an abstract or general idea or term",

encapsulate: "to place in or as if in a capsule",

object: "anything that is visible or tangible and is relatively stable in form",

descriptor: function () {

return map(['abstraction', 'encapsulate', 'object'], function (key) {

return key + ': "' + dictionary[key] + '"';

}).join('; ');

}

};

dictionary.descriptor()

//=>

'abstraction: "an abstract or general idea or term"; encapsulate: "to place i\

n or as if in a capsule";

object: "anything that is visible or tangible and is relatively stable in for\

m"'

In this example, descriptor is a method. As we saw earlier, this code has many problems, but let’s hand-wave them for a moment. Let’s be clear about our terminology. This dictionary object has a method called descriptor. The function associated with descriptor is called the method handler.

When we write dictionary.descriptor(), we’re invoking or calling the descriptor method. The object is then handling the method invocation by evaluating the function.

In describing objects, we refer to objects as encapsulating their internal state. The ideal is that objects never directly access or manipulate each other’s state. Instead, objects interact with each other solely through methods.

There are many things that methods can do. Two of the most obvious are to query an object’s internal state and to update its state. Methods that have no purpose other than to report internal state are called queries, while methods that have no purpose other than to update an object’s internal state are called updates.

The Letter and the Spirit of the Law

The ‘law’ that objects must only interact with each other through methods is generally accepted as an ideal, even though languages like JavaScript and Java do not enforce it. That being said, there are (roughly) two philosophies about the design of objects and their methods.

· Literalists believe that the important thing is the means of interaction be methods. Literalists are noted for using a profusion of getters and setters, which leads to code that is semantically identical to code where objects interact with each other’s internal state, but every interaction is performed indirectly through a query or update.

· Semanticists believe that the important thing is that objects provide abstractions over their internal state. They avoid getters and setters, preferring to provide methods that are a level of abstraction above their internal representations.

By way of example, let’s imagine that we have a person object. Our first cut at it involves storing a name. Here’s a first cut at a literalist implementation:

var person = Object.create(null, {

_firstName: {

enumerable: false,

writable: true

},

_lastName: {

enumerable: false,

writable: true

},

getFirstName: {

enumerable: false,

writable: true,

value: function () {

return _firstName;

}

},

setFirstName: {

enumerable: false,

writable: true,

value: function (str) {

return _firstName = str;

}

},

getLastName: {

enumerable: false,

writable: true,

value: function () {

return _lastName;

}

},

setLastName: {

enumerable: false,

writable: true,

value: function (str) {

return _lastName = str;

}

}

});

This is largely pointless as it stands. Other objects now write person.setFirstName('Bilbo') instead of person.firstName = 'Bilbo', but nothing of importance has been improved. The trouble with this approach as it stands is that it is a holdover form earlier times. Much of the trouble stems from design decisions made in languages like C++ and Java to preserve C-like semantics.

In those languages, once you have some code written as person.firstName = 'Bilbo', you are forever stuck with person exposing a property to direct access. Without changing the semantics, you may later want to do something like make person observable, that is, we add code such that other objects can be notified when the person object’s name is updated.

If firstName is a property being directly updated by other entities, we have no way to insert any code to handle the updating. The same argument goes for something like validating the name. Although validating names is a morass in the real world, we might have simple ideas such as that the first name will either have at least one character or be null, but never an empty string. If other entities directly update the property, we can’t enforce this within our object.

In days of old, programmers would have needed to go through the code base changing person.firstName = 'Bilbo' into person.setName('Bilbo') (or even worse, adding all the observable code and validation code to other entities).

Thus, the literalist tradition grew of defining getters and setters as methods even if no additional functionality was needed immediately. With the code above, it is straightforward to introduce validation and observability:21

var person = Object.create(null, {

// ...

setFirstName: {

enumerable: false,

writable: true,

value: function (str) {

// insert validation and observable boilerplate here

return _firstName = str;

}

},

setLastName: {

enumerable: false,

writable: true,

value: function (str) {

// insert validation and observable boilerplate here

return _lastName = str;

}

}

});

That seems very nice, but balanced against this is that contemporary implementations of JavaScript allow you to write getters and setters for properties that mediate access even when other entities are using property access syntax like person.lastName = 'Baggins':

var person = Object.create(null, {

_firstName: {

enumerable: false,

writable: true

},

firstName: {

get: function () {

return _firstName;

},

set: function (str) {

// insert validation and observable boilerplate here

return _firstName = str;

}

},

_lastName: {

enumerable: false,

writable: true

},

lastName: {

get: function () {

return _lastName;

},

set: function (str) {

// insert validation and observable boilerplate here

return _lastName = str;

}

}

});

The preponderance of evidence suggests that if you are a literalist, you are better off not bothering with making getters and setters for everything in JavaScript, as you can add them later if need be.

the semantic interpretation of object methods22

What about the semantic approach?

With the literalist, properties like firstName are decoupled from methods like setFirstName so that the implementation of the properties can be managed by the object. Other objects calling person.setFirstName('Frodo') are insulated from details such as whether other objects are to be notified when person is changed.

But while the implementation is hidden, there is no abstraction involved. The level of abstraction of the properties is identical to the level of abstraction of the methods.

The semanticist takes this one step further. To the semanticist, objects insulate other entities from implementation details like observables and validation, but objects also provide an abstraction to other entities.

In our person example, first and last name is a very low-level concern, the kind of thing you think about when you’re putting things in a database and worrying about searching and sorting performance. But what would be a higher-level abstraction?

Just a name.

You ask someone their name, they tell you. You ask for a name, you get it. An object that takes and accepts names hides from us all the icky questions like:

1. How do we handle people who only have one name? (it’s not just celebrities)

2. Where do we store the extra middle names like Tracy Christopher Anthony Lee?

3. How do we handle formal Spanish names like Gabriel José de la Concordia García Márquez?

4. What do we do with maiden names like Arlene Gwendolyn Lee née Barzey or Leocadia Blanco Álvarez de Pérez?

If we expose the low-level fields to other code, we demand that they know all about our object’s internals and do the parsing where required. It may be simpler and easier to simply expose:

var person = Object.create(null, {

_givenNames: {

enumerable: false,

writable: true,

value: []

},

_maternalSurname: {

enumerable: false,

writable: true

},

_paternalSurname: {

enumerable: false,

writable: true

},

_premaritalName: {

enumerable: false,

writable: true

},

name: {

get: function () {

// ...

},

set: function (str) {

// ...

}

}

});

The person object can then do the “icky” work itself. This centralizes responsibility for names.

Now, honestly, people have been handling names in a very US-centric way for a very long time, and few will put up a fuss if you make objects with highly literal name implementations. But the example illustrates the divide between a literal design where other objects operate at the same level of abstraction as the object’s internals, and a semantic design, one that operates at a higher level of abstraction and is responsible for translating methods into queries and updates on the implementation.

Composite Methods

One of the primary activities in programming is to factor programs or algorithms, to break them into smaller parts that can be reused or recombined in different ways.

information

Common industry practice is to use the words “decompose” and “factor” interchangeably to refer to any breaking of code into smaller parts. Nevertheless, we will defy industry practice and use the word “decompose” to refer to breaking code into smaller parts whether those parts are to be recombined or reused or not, and use the word “factor” to refer to the stricter case of decomposition where the intention is to recombine or reuse the parts in different ways.

Both methods and objects can and should be factored into reusable components that have a single, well-defined responsibility.23

The simplest way to decompose a method is to “extract” one or more helper methods. For example:

var person = Object.create(null, {

// ...

setFirstName: {

enumerable: false,

writable: true,

value: function (str) {

if (typeof(str) === 'string' && str != '') {

return this._firstName = str;

}

}

},

setLastName: {

enumerable: false,

writable: true,

value: function (str) {

if (typeof(str) === 'string' && str != '') {

return this._lastName = str;

}

}

}

});

The methods setFirstName and setLastName both have a “guard clause” that will not update the object’s hidden state unless the method is passed a non-empty string. The logic can be extracted into its own “helper method:”

var person = Object.create(null, {

// ...

isaValidName: {

enumerable: false,

writable: false,

value: function (str) {

return (typeof(str) === 'string' && str != '');

}

},

setFirstName: {

enumerable: false,

writable: true,

value: function (str) {

if (this.isaValidName(str)) {

return this._firstName = str;

}

}

},

setLastName: {

enumerable: false,

writable: true,

value: function (str) {

if (this.isaValidName(str)) {

return this._lastName = str;

}

}

}

});

The methods setFirstName and setLastName now call the helper method isaValidName. The usual motivation for this is known as DRY or “Don’t Repeat Yourself.” The DRY principle is stated as “Every piece of knowledge must have a single, unambiguous, authoritative representation within a system.”

In this case, presumably there is one idea, “person names must be non-empty strings,” and placing the implementation for this in the isaValidString helper method ensures that now there is just the one authoritative source for the logic, instead of one in each name setter method.

Decomposing a method needn’t always be for the purpose of DRYing up the logic. Sometimes, a method breaks down logically into a hierarchy of steps. For example:

var person = Object.create(null, {

// ...

doSomethingComplicated: {

enumerable: false,

writable: true,

value: function () {

this.setUp();

this.doTheWork();

this.breakdown();

this.cleanUp();

}

},

setUp: // ...

doTheWork: // ...

breakdown: // ...

cleanUp: // ...

});

This is as true of methods as it is of functions in general. However, objects have some extra considerations. The most conspicuous is that an object is its own namespace. When you break a method down into helpers, you are adding items to the namespace, making the object as a whole more difficult to understand. What methods call setUp? Can breakdown be called independently of cleanUp? Everything is thrown into an object higgledy-piggledy.

decluttering with closures

JavaScript provides us with tools for reducing object clutter. The first is the Immediately Invoked Function Expression (“IIFE”). If our four helpers exist only to decompose doSomethingComplicated, we can write:

var person = Object.create(null, {

// ...

doSomethingComplicated: {

enumerable: false,

writable: true,

value: (function () {

return function () {

setUp.call(this);

doTheWork.call(this);

breakdown.call(this);

cleanUp.call(this);

};

function setUp () {

// ...

}

function doTheWork () {

// ...

}

function breakdown () {

// ...

}

function cleanUp () {

// ...

}

})()

},

});

Now our four helpers exist only within the closure created by the IIFE, and thus it is impossible for any other method to call them. You could even create a setUp helper with a similar name for another function without clashing with this one. Note that we’re not invoking these functions withthis., because they aren’t methods any more. And to preserve the local object’s context, we’re calling them with .call(this).

decluttering with method objects

In JavaScript, methods are represented by functions. And in JavaScript, functions are objects. Functions have properties, and the properties behave just like objects we create with {} or Object.create:

var K = function (x) {

return function (y) {

return x;

};

};

Object.defineProperty(K, 'longName', {

enumerable: true,

writable: false,

value: 'The K Combinator'

});

K.longName

//=> 'The K Combinator'

Object.keys(K)

//=> [ 'longName' ]

We can take advantage of this by using a function as a container for its own helper functions. There are several easy patterns for this. Of course, you could write it all out by hand:

function doSomethingComplicated () {

doSomethingComplicated.setUp.call(this);

doSomethingComplicated.doTheWork.call(this);

doSomethingComplicated.breakdown.call(this);

doSomethingComplicated.cleanUp.call(this);

}

doSomethingComplicated.setUp = function () {

// ...

}

doSomethingComplicated.doTheWork = function () {

// ...

}

doSomethingComplicated.breakdown = function () {

// ...

}

doSomethingComplicated.cleanUp = function () {

// ...

}

var person = Object.create(null, {

// ...

doSomethingComplicated: {

enumerable: false,

writable: true,

value: doSomethingComplicated

}

});

If we’d like to make it neat and tidy inline, tap is handy:

var person = Object.create(null, {

// ...

doSomethingComplicated: {

enumerable: false,

writable: true,

value: tap(

function doSomethingComplicated () {

doSomethingComplicated.setUp.call(this);

doSomethingComplicated.doTheWork.call(this);

doSomethingComplicated.breakdown.call(this);

doSomethingComplicated.cleanUp.call(this);

}, function (its) {

its.setUp = function () {

// ...

}

its.doTheWork = function () {

// ...

}

its.breakdown = function () {

// ...

}

its.cleanUp = function () {

// ...

}

})

}

});

In terms of code, this is no simpler than the IIFE solution. However, placing the helper methods inside the function itself does make them available for use or modification by other methods. For example, you can now use a method decorator on any of the helpers:

var logsTheReciver = after( function (value) {

console.log(this);

return value;

});

person.doSomethingComplicated.doTheWork = logsTheReciver(person.doSomethingCompli\

cated.doTheWork);

This would not have been possible if doTheWork was hidden inside a closure.

summary

Like “ordinary” functions, methods can benefit from being decomposed or factored into smaller functions. Two of the motivations for doing so are to DRY up the code and to break a method into more easily understood and obvious parts. The parts can be represented as helper methods, functions hidden in a closure, or properties of the method itself.

Method Objects

function helpers

If functions are objects, and functions can have properties, then functions can have methods. We can give functions our own methods by assigning functions to their properties. We saw this previously when we decomposed an object’s method into helper methods. Here’s the same applied to a function:

function factorial (n) {

return factorial.helper(n, 1);

}

factorial.helper = function helper (n, accumulator) {

if (n === 0) {

return accumulator;

}

else return helper(n - 1, n * accumulator);

}

Functions can have all sorts of properties. One of the more intriguing possibility is to maintain an array of functions:

function sequencer (arg) {

var that = this;

return sequencer._functions.reduce( function (acc, fn) {

return fn.call(that, acc);

}, arg);

}

Object.defineProperties(sequencer, {

_functions: {

enumerable: false,

writable: false,

value: []

}

});

sequencer is an object-oriented way to implement the pipeline function from function-oriented libraries like allong.es. Instead of writing something like:

function square (n) { return n * n; }

function increment (n) { return n + 1; }

pipeline(square, increment)(6)

//=> 37

We can write:

sequencer._functions.push(square);

sequencer._functions.push(increment);

sequencer(6)

//=> 37

The functions contained within the _functions array are helper methods. They work they same way as is we’d written something like:

function squarePlusOne (arg) {

return squarePlusOne.increment(

squarePlusOne.square(arg)

);

}

squarePlusOne.increment = increment;

squarePlusOne.square = square;

squarePlusOne(6)

//=> 37

The only difference is that they’re dynamically looked up helper methods instead of statically wired in place by the body of the function. But they’re still helper methods.

function methods

The obvious problem with our approach is that we aren’t using our method as an object in the ideal sense. Why should other entities manipulate its internal state? If this were any other kind of object, we’d expose methods handle messages from other entities.

Let’s try it:

Object.defineProperties(sequencer, {

push: {

enumerable: false,

writable: false,

value: function (fn) {

return this._functions.push(fn);

}

}

});

Now we can manipulate our sequencer without touching its privates:

sequencer.push(square);

sequencer.push(increment);

sequencer(6)

//=> 37

Is it a big deal to eliminate the _functions reference? Yes!

1. This hides the implementation: Do we have an array of functions? Or maybe we are composing the functions with a combinator? Who knows?

2. This prevents us manipulating internal state in unauthorized ways, such as calling sequencer._functions.reverse()

sequencer.push is a proper method, a function that handles messages from other entities and in the course of handling the message, queries and/or updates the function’s internal state.

If we’re going to treat functions like objects, we ought to give them methods.

aspect-oriented programming and meta-methods

When an object has a function as one of its properties, that’s a method. And we just established that functions can have methods. So… Can a method have methods?

Most assuredly.

Here’s an example that implements a simplified form of Aspect-Oriented Programming

Consider a businessObject and a businessObjectCollection. Never mind what they are, that isn’t important. We start with the idea that a businessObject can be valid or invalid, and there’s a method for querying this:

var businessObject = Object.create(null, {

// ...

isValid: {

enumerable: false,

value: function () {

// ...

}

}

});

Obviously, the businessCollection has methods for adding business objects and for finding business objects:

var businessCollection = Object.create(null, {

// ...

add: {

enumerable: false,

value: function (bobj) {

// ...

}

},

find: {

enumerable: false,

value: function (fn) {

// ...

}

}

});

Our businessCollection is just a collection, it doesn’t actually do anything to the business objects it holds.

One day, we decide that it is an error if an invalid business object is placed in a business collection, and also an error if an invalid business object is returned when you call businessCollection.find. To “fail fast” in these situations, we decide that an exception should be thrown when this happens.

Should we add some checking code to businessCollection.add and businessCollection.find? Yes, but we shouldn’t modify the methods themselves. Since businessCollection’s single responsibility is storing objects, business rules about the state of the objects being stored should be placed in some other code.

What we’d like to do is “advise” the add method with code that is to be run before it runs, and advise the find method with code that runs after it runs. If our methods had methods, we could write:

businessCollection.add.beforeAdvice(function (bobjProvided) {

if (!bobjProvided.isValid())

throw 'bad object provided to add';

});

businessCollection.find.afterAdvice(function (bobjReturned) {

if (bobjReturned && !bobjReturned.isValid())

throw 'bad object returned from find';

});

As you can see, we’ve invented a little protocol. .beforeAdvice adds a function “before” a method, and .afterAdvice adds a function “after” a method. We’ll need a function to make method objects out of the desired method functions:

function advisable (methodBody) {

function theMethod () {

var args = [].slice.call(arguments);

theMethod.befores.forEach( function (advice) {

advice.apply(this, args);

}, this);

var returnValue = theMethod.body.apply(this, arguments);

theMethod.afters.forEach( function (advice) {

advice.call(this, returnValue);

}, this);

return returnValue;

}

Object.defineProperties(theMethod, {

befores: {

enumerable: true,

writable: false,

value: []

},

body: {

enumerable: true,

writable: false,

value: body

},

afters: {

enumerable: true,

writable: false,

value: []

},

beforeAdvice: {

enumerable: false,

writable: false,

value: function (fn) {

this.befores.unshift(fn);

return this;

}

},

afterAdvice: {

enumerable: false,

writable: false,

value: function (fn) {

this.afters.push(fn);

return this;

}

}

});

return theMethod;

}

Let’s rewrite our businessCollection to use our advisable function:

var businessCollection = Object.create(null, {

// ...

add: {

enumerable: false,

value: advisable(function (bobj) {

// ...

})

},

find: {

enumerable: false,

value: advisable(function (fn) {

// ...

})

}

});

And now, exactly as above, we can write businessCollection.add.beforeAdvice and businessCollection.find.afterAdvice, separating the responsibility for error checking from the responsibility for managing a collection of business objects.

Predicate Dispatch

Pattern matching is a feature found (with considerable rigor) in functional languages like Erlang and Haskell. In mathematics, algorithms and problems can often be solved by breaking them down into simple cases. Sometimes, those cases are reductions of the original problem.

A famous example is the naïve expression of the factorial function. The factorial of a non-negative integer n is the product of all positive integers less than or equal to n. For example, factorial(5) is equal to 5 * 4 * 3 * 2 * 1, or 120.

The algorithm to compute it can be expressed as two cases. Let’s pretend that JavaScript has predicate-dispatch baked in:

function factorial (1) {

return 1;

}

function factorial (n > 1) {

return n * factorial(n - 1);

}

This can be done with an if statement, of course, but the benefit of breaking problems down by cases is that once again, we are finding a way to combine small pieces of code in a way that does not tightly couple them.

We can fake this with a simple form of predicate dispatch, and we’ll see later that it will be very useful for implementing multiple dispatch.

prelude: return values

Let’s start with a convention: Methods and functions must return something if they successfully hand a method invocation, or raise an exception if they catastrophically fail. They cannot return undefined (which in JavaScript, also includes not explicitly returning something).

For example:

// returns a value, so it is successful

function sum (a, b) {

return a + b;

}

// returns this, so it is successful

function fluent (x, y, z) {

// do something

return this;

}

// returns undefined, so it is not successful

function fail () {

return undefined;

}

// decorates a function by making it into a fail

function dont (fn) {

return fail;

}

// logs something and fails,

// because it doesn't explicitly return anything

function logToConsole () {

console.log.apply(null, arguments);

}

guarded functions

We can write ourself a simple method decorator that guards a method, and fails if the guard function fails on the arguments provided. It’s self-currying to facilitate writing utility guards:

function nameAndLength(name, length, body) {

var abcs = [ 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',

'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',

'z', 'x', 'c', 'v', 'b', 'n', 'm' ],

pars = abcs.slice(0, length),

src = "(function " + name + " (" + pars.join(',') + ") { return body.apply\

(this, arguments); })";

return eval(src);

}

function imitate(exemplar, body) {

return nameAndLength(exemplar.name, exemplar.length, body);

}

function when (guardFn, optionalFn) {

function guarded (fn) {

return imitate(fn, function () {

if (guardFn.apply(this, arguments))

return fn.apply(this, arguments);

});

}

return optionalFn == null

? guarded

: guarded(optionalFn);

}

when(function (x) {return x != null; })(function () { return "hello world"; })();

//=> undefined

when(function (x) {return x != null; })(function () { return "hello world"; })(1);

//=> 'hello world'

when is useful independently of our work here, and that’s a good thing: Whenever possible, we don’t just make complicated things out of simpler things, we make them out of reusable simpler things. Now we can compose our guarded functions. Match takes a list of methods, and apply them in order, stopping when one of the methods returns a value that is not undefined.

function getWith (prop, obj) {

function gets (obj) {

return obj[prop];

}

return obj === undefined

? gets

: gets(obj);

}

function mapWith (fn, mappable) {

function maps (collection) {

return collection.map(fn);

}

return mappable === undefined

? maps

: maps(collection);

}

function pluckWith (prop, collection) {

var plucker = mapWith(getWith(prop));

return collection === undefined

? plucker

: plucker(collection);

}

function Match () {

var fns = [].slice.call(arguments, 0),

lengths = pluckWith('length', fns),

length = Math.min.apply(null, lengths),

names = pluckWith('name', fns).filter(function (name) { return name !== '\

'; }),

name = names.length === 0

? ''

: names[0];

return nameAndLength(name, length, function () {

var i,

value;

for (i in fns) {

value = fns[i].apply(this, arguments);

if (value !== undefined) return value;

}

});

}

function equals (x) {

return function eq (y) { return (x === y); };

}

function not (fn) {

var name = fn.name === ''

? "not"

: "not_" + fn.name;

return nameAndLength(name, fn.length, function () {

return !fn.apply(this, arguments)

});

}

var worstPossibleTestForEven = Match(

when(equals(0), function (n) { return true; }),

when(equals(1), function (n) { return false; }),

function (n) { return worstPossibleTestForOdd(n - 1)}

)

var worstPossibleTestForOdd = Match(

when(equals(0), function (n) { return false; }),

when(equals(1), function (n) { return true; }),

function (n) { return worstPossibleTestForEven(n - 1)}

)

worstPossibleTestForEven(6)

//=> true

worstPossibleTestForOdd(42)

//=> false

This is called predicate dispatch, we’re dispatching a function call to another function based on a series of predicates we apply to the arguments. Predicate dispatch declutters individual cases and composes functions and methods from smaller, simpler components that are decoupled from each other.