Noel Rappin Writes Here

Functions that return functions are the luckiest functions in the world

Posted on September 25, 2012


Here’s some JavaScript:

var foo = function(a, b) { return a * b };

var bar = function(func) {
  return function() {
    return func.apply(this, arguments)
  };
};

baz = bar(foo);
console.log(baz(2, 3));

What we have here is a function, bar, that both takes a function as an argument and returns a function as it’s result. It’s transforming the function passed to it into a different function.

Okay, that’s cool in kind of an abstract way, but so what? Let’s play around with function that take and return functions to show off some potentially useful tricks.

I’ll note right here up top, that there are more complex, fully featured, and robust versions of all the things I’m showing here. I’m just playing with functions.

We can add diagnostics.

var foo = function(a, b) { return a * b };

var trace = function(func) {
  return function() {
    console.log("calling " + func);
    console.log(new Error("normal trace").stack);
    return func.apply(this, arguments)
  };
};

foo = trace(foo);
console.log(foo(2, 3));

All the trace function is doing here is logging a couple of things before actually calling the function — namely, it’s printing the function body (JavaScript doesn’t have a consistent way to get the function’s name), and a stack trace. The console output is:

calling function (a, b) { return a * b }
Error: normal trace
  at file:///Users/nrappin/Dropbox/coderx/blog/functions.html:8:17
  at file:///Users/nrappin/Dropbox/coderx/blog/functions.html:15:13

Okay, that’s kind of neat. You can also tweak it to do something like a Jasmine spy object, but it takes some object manipulation. Here we really start to take advantage of the fact that JavaScript functions are basically just JavaScript objects.

var foo = function(a, b) { return a * b };

var spy = function(func) {
  var returnFunc = function() {
    returnFunc.timesCalled += 1;
    returnFunc.args.push(arguments);
    return func.apply(this, arguments);
  }
  returnFunc.timesCalled = 0;
  returnFunc.args = [];
  return returnFunc;
};

foo = spy(foo);

console.log(foo(2, 3));
console.log(foo.timesCalled);
console.log(foo.args)

Same basic idea here, except we’re doing some dancing with scope to make sure we have access to all the variables we want to, when we want them. Specifically, rather than just returning an anonymous function, we’re giving it the name returnFunc. Inside the funtion body of returnFunc, we’re accessing properties of the returnFunc functional object itself. Specifically we’re incrementing a counter and adding to a list of called arguments.

After we define the function, we initialize the timesCalled and args properties, and return our functional object. This works because we can access properties of the functional object inside the function itself, which seems weird, but makes more sense if you think of it just as an ordinary object accessing other properties of the same object.

Here’s a related example — in this case, our function object has its own methods.

var benchmark = function(func) {
  var returnFunc = function() {
    returnFunc.startDate = Date.now();
    result = func.apply(this, arguments);
    returnFunc.endDate = Date.now();
    return result
  }
  returnFunc.elapsedTime = function() {
    return returnFunc.endDate - returnFunc.startDate;
  };
  return returnFunc;
}

foo = benchmark(foo);
console.log(foo(2, 3));
console.log(foo.elapsedTime());

In this one, we’re running the inner function surrounded by time stamps, and we’re adding a function to the function object, which again, seems weird. At the end, we can query a benchmarked function as to how long it takes. (Yes, I realize that the dev tools allow you to do this.)

Here’s one that might be more practical. Memoizing is the act of caching the results of function calls so that you don’t need to re-evaluate an expensive computation.

var memoize = function(func) {
  var returnFunc = function() {
    argsToCheck = (JSON.stringify(arguments));
    if (returnFunc.cache[argsToCheck]) {
      return returnFunc.cache[argsToCheck];
    } else {
      result = func.apply(this, arguments);
      returnFunc.cache[argsToCheck] = result;
       return result;
    }
  }
  returnFunc.cache = {};
  return returnFunc;
}

foo = memoize(foo);
console.log(foo(2, 3));

In this case, we hold on to the functional argument as before, but when we call the memoized function, we first compare the argument list against a cache. We’re using the JSON string version of the argument list, so we do have that conversion overhead. If this particular argument list has already been called, then we return the result of the previous call without recalculating. If not, we calculate the function, and place the result in the cache. So, our first call to foo in the example places (2, 3) in the cache, but the second call would retrieve it, to avoid that very expensive multiplication.

This is the beginning of manipulating functions in JavaScript — functional programming in general is a big topic, but worth looking at. Adapting a functional style where side effects and mutable state are downplayed in favor of manipulating functions, can really pay off in less complexity in your code.

Like this? You might like my book Master Time and Space With JavaScript, free sample available, and $15 for the whole thing.



Comments

comments powered by Disqus



Copyright 2024 Noel Rappin

All opinions and thoughts expressed or shared in this article or post are my own and are independent of and should not be attributed to my current employer, Chime Financial, Inc., or its subsidiaries.