Callable Objects in JavaScript
Recently, Chris Fedinandi wrote about the constructor pattern and when you might use it. This got me thinking about a design pattern in JavaScript of which I’m fond—I think of the pattern as “callable objects”. I first encountered this pattern in D3, where it is used extensively to create utilities like scales.
What are callable objects?
Callable objects are objects that you can call like a function, and which maintain some internal state. They have getters and setters for managing state, and when invoked like a function, the internal state of the object determines the behavior of the function.
They can be useful when you want to perform some operation multiple times where only one of several arguments to the operation is changing each time you call it. Because you have an object that stores it’s own state, you can set the state on the object and hold onto that reference, then you just call it like a function whenever you need.
A trivial example
Let’s take a simple greeter function as an example. The function combines a salutation and a subject to form the greeting.
> greet("Hello", "world")
"Hello, world"
> greet("Ahoy", "matey")
"Ahoy, mate"
greet
function
If I were to implement this as a callable object, I’d probably make
the salutation part of the object state so that we could set it once
and then use the greeter to greet multiple subjects. To use it, we
would first create an instance of the greeter function, then we use
the salutation
accessor to set our salutation. From then
on, we can just call the greeter instance with the subject we want to
greet and it will use the salutation we set on it.
> const hello = greeter()
> hello.salutation("Hello")
> hello("world")
"Hello, world"
This can come in handy if we’re applying this function to multiple arguments.
> for (let subject of ["Linus", "Lucy", "Snoopy"]) {
... console.log(hello(subject))
... }
Hello, Linus
Hello, Lucy
Hello, Snoopy
And it’s especially handy if you want to pass the function off to some other function as an argument.
> ["Linus", "Lucy", "Snoopy"].map(hello)
[ "Hello, Linus", "Hello, Lucy", "Hello, Snoopy" ]
Array.prototype.map
Implementing a callable object
Implementing this pattern in JavaScript is relatively straightforward. All you have to do is write a function that returns another function.
function greeter() {
function call() {}
return call;
}
Every time you invoke greeter()
, you’ll get a new copy of
this call()
function. The call()
function is
where we’ll implement the actual greeting logic. We’ll want
call()
to take a subject to greet as an argument, so for
starters we’ll implement it like this:
function greeter() {
function call(subject) {
return `Hello, ${subject}`;
}
return call;
}
call()
function
The next thing we need to do is add a property to the greeter for the
salutation to replace the hard-coded "Hello"
.
We’ll add a variable to the closure to hold the state, and add a
property to our call()
function to get and set the
salutation variable.
function greeter() {
// "Private" variable for containing the salutation used by
// our greeter, with a default value.
let salutation = "Hello";
function call(subject) {
// Combine the salutation variable in the closure with the
// subject argument to form the greeting.
return `${salutation}, ${subject}`;
}
// Accessor for the salutation. When called with no argument,
// it acts as a getter, otherwise it sets the salutation to
// `value`
call.salutation = function (value) {
if (arguments.length < 1) return salutation;
salutation = value;
};
return call;
}
If you’re thinking that this looks an awful lot like currying a
function, you’re not wrong. The big difference is that instead of
passing in the state when we call greeter()
, we set the
state using accessors. This has advantages in some situations. It
allows us to change state later without having to call
greeter()
again to create a new callable. It also allows
us to defer some of our configuration to a later time.
Spoiled for choice
Personally, I’m a fan of this particular pattern, but JavaScript is flexible and there are plenty of other patterns you could use instead of creating a callable object. Our trivial greeter example would do just fine as a simple function, and you could always use an anonymous function if you needed to hold the salutation constant for some reason.
> const hello = (subject) => greet("Hello", subject)
If you had a lot of state that makes for too many arguments passed to one function, you could go the class route. In this case, instead of invoking the object directly, you invoke a method on it. You know: object-orient programming, if that’s your thing.
> const helloGreeter = new Greeter()
> helloGreeter.salutation = "Hello"
> helloGreeter.greet("world")
"Hello, world"
It’s possible that callable objects are appealing to me because they’re so common in D3, and I really like working with D3. Why D3 makes so much sense to me, though, I can’t say.