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
Comments
We moved off of Disqus for data privacy and consent concerns, and are currently searching for a new commenting tool.
too complicated for me.
This might backfire for you, as it seems to demonstrate that ES6 syntax removes comprehensibility. That may be fine for \”real programmers\”, but requiring the average reader to now hold in their head the nested scopes implied by those piles of brackets, and to simultaneously understand the implicit returns, all for saving a few keystrokes, seems to cost more than it saves.
The nested scopes aren’t implied though \u2014 the brackets you mention explicit invoke them. In terms of legibility, the extra curly brackets and extra text volume of the words `function` and `return` may provide helpful delimiters, but the fat arrow syntax is actually unambiguous.
If you want your source code for the Y combinator to be as clear as possible in its inner workings, you might want to introduce comments and a great deal more whitespace. But when dealing with large volumes of source code, I find less syntactic cruft actually helps highlight inner workings at a glance.
The use of \”implied\” re: brackets (as opposed to returns) was conversational. I apologize for the confusion.
If I took 100 developers and gave them those two code snippets and had them explain what is happening, > 60% would \”get\” the ES6 syntax more quickly? I really need to know more about how you’ve arrived at that conclusion.
I do agree that more whitespace and comments are truly the way to writing maintainable code.
No, I think out of a random sampling of Javascript developers, most would probably be more familiar with the pre-ES6 functionality \u2014 but only because most wouldn’t have seen => syntax before. This is an education problem \u2014 but depending on your codebase community probably not one worth dealing with until ES6 has more stability & widespread adoption.
If you were to take 100 non-JS programmers, people would be less distracted by verbose JS-specific syntax cruft, and see the workings a bit clearer. Especially if they were Python, Ruby or Coffeescript programmers, the specifics of the syntax would already be familiar to them.
Just curious, now that you’ve probably had some significant time to familiarize yourself with arrows and have likely seen them more in the wild, do you still feel this way?
It’s interesting how easily you led yourself into believing that someone disagreeing with you probably doesn’t have your intelligence or experience. I’ll play along. Let’s assume now that I have sufficient experience to justify my answer:
Yes.
P.S. This is a great history of the medium: https://eager.io/blog/the-l…
In particular, this line: \”DSSSL did, unfortunately, have the fatal flaw which would plague all Scheme-like languages: too many parenthesis.\”
Now that you’ve had some significant time to familiarize yourself with parentheses and have likely seen them more in the wild, what do you think of that opinion? I would agree with it, FWIW.
if you can’t take the heat stay out of the kitchen
Why the need to make `fac` a parameter? Why can’t you just use
var factorial = function() {
// use factorial variable here
};
It seems unnecessary indirection to make the code look more like another language that it isn’t.
In CoffeeScript:
https://gist.github.com/bre…
I didn’t like these features.
That the Y-combinator is an elegant little tool, creating recursion even where no explicit recursion is allowed, is quite clear. But to call the JS formulation of it \”beautiful Javascript\” is quite odd. In pure lambda-calculus form, this is perhaps beautiful. In Javascript, it’s simply an exotic demonstration that \”Hey, this stuff works here, too!\” I would say that it’s far from beautiful Javascript.
But that does not take away from the fact that the ES6 version is fairly elegant.
Let’s just remember that there is really no practical purpose to the Y-combinator in a language where recursion is always explicit. (Which is not to say that it isn’t very interesting, and a great thing to whip out in arguments… 🙂 ).
Erik Eckhardt this technique is meant to build recursive functions using anonymous functions, i.e. there are no references to functions. It comes from the lambda calculus which is the theoretical foundation for functional programming. I guess this is a way to verify its results are valid in javascript, or just a fun exercise.