Skip To Main Content

The Little JavaScripter, Revisited

Posted by Rick Waldron

Feb 07 2014

Many readers will recognize the following program, which is an adaptation of The Little Schemer’s Y combinator implementation; written and published by Douglas Crockford in 2003 to demonstrate the commonalities found between JavaScript and Scheme. If you’re unfamiliar with recursion, fixed point combinators or the “Y combinator”, take a look at the Wikipedia article and dig in futher with Recursion and the Y Combinator.

I’ve always felt that this was an example of truly beautiful JavaScript.

y-combinator-es3.js

function Y(le) {
  return (function (f) {
    return f(f);
  }(function (f) {
    return le(function (x) {
      return f(f)(x);
    });
  }));
}

var factorial = Y(function (fac) {
  return function (n) {
    return n <= 2 ? n : n * fac(n - 1);
  };
});

factorial(5); // 120

Note: The following examples correct the factorial logic to return 1 if the value is less than two which is irrelevant to the focus of this article.

First, we agree that there is nothing technically wrong with this code; second, we agree that there is a lot of unnecessary ceremonial boilerplate which we no longer need thanks to syntactic simplifications being introduced in ECMAScript, 6th Edition. The words “function” and “return” both appear six times each, in pairs for every function in the entire program—of which there are six. There are thirty parenthesis: fifteen opening and fifteen closing. There are twelve curly braces: six opening and six closing. Most of that has very little to do with expressing the actual functionality, so let’s get rid of it! The following program is observably the same as the previous program, utilizing new syntactic forms of ES6, bringing the code closer to the expressiveness of Scheme—and yet more concise.

y-combinator-es6.js

let Y =
  (le => (f => f(f))
    (f => le((...args) => f(f)(...args))));

let factorial =
  Y(f => (n =>
    (n < 2 ?
      1 :
      n * f(n - 1))));

factorial(5); // 120

Here’s what happened

In Doug’s original example every function returned an expression whose value was either another function or the result of evaluating an expression, so each traditional function expression can easily be replaced by an Arrow Function in its concise, assignment expression body form, which is an implied return. In doing so, we have effectively freed the source from burdensome “function + return” pairs. Keep in mind that the scope of each Arrow Function is that of its call site’s scope, which is a slight semantic deviation from the ES3 function expressions that Doug was working with, which have their own function scope.

The single formal parameter x was replaced by a rest parameter named args. The single x argument was replaced by spread args—aligning the arity with the original Scheme examples.

For contextual comparison, this is the same program written in Scheme:

y-combinator-scheme.rkt

(define (Y f)
  ((lambda (x) (x x))
   (lambda (g)
     (f (lambda args (apply (g g) args))))))

(define fac
  (Y
    (lambda (f)
      (lambda (x)
        (if (< x 2)
          1
          (* x (f (- x 1))))))))

(fac 5) ; 120

Posted by
Rick Waldron
on February 7th, 2014

Comments

We moved off of Disqus for data privacy and consent concerns, and are currently searching for a new commenting tool.

Contact Us

We'd love to hear from you. Get in touch!