私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「姿 (変数名) を 隠す」(CodeIQ)を参照してください。
var factorial = function (n) {
'use strict';
if (n === 1) {
return 1;
}
return n * factorial(n - 1);
};
console.log(factorial(10));
というコードがあるとします。Javascript(NodeJs)で解答しています。
console.log(
(function(f) {
'use strict';
return (function(x) {
return f(function(y) {
return x(x)(y);
});
})(function(x) {
return f(function(y) {
return x(x)(y);
});
});
})(function(f) { return function(n) { return n == 0 ? 1 : n * f(n - 1); }; })(10)
);
無名関数をどうやって再帰するかという問題です。
無名関数には名前がないので再帰呼び出し時に関数名を使えないため何らかの方法でそれを実現しなければなりません。
この問題では定義した無名関数を即時実行しなければなりません。それができないと一度変数にとって呼び出す必要があるため問題の仕様を満たせないのです。
その手段が無名関数を(カッコ)で囲むという方法です。
// 10と出力される
(function(n){
console.log(n);
})(10);
これで、無名関数の引数n=10で即時実行されます。
この方法はJavaScriptでクラスのようなものを作るときにも使います。
解答のコードは2つ目に書いたコードで、最初(提出しませんでしたが)別のコードを書きました。
console.log(
(function(n){
if(n == 1){
return 1;
}
return n*arguments.callee(n-1);
})(10)
);
解答のコードよりも短く、わかりやすいです。
問題の関数を無名関数にして即時呼び出ししています。無名関数を再帰呼び出しするためarguments.callee()を使用します。
ただ、1点気になる点があってこのコードは提出しませんでした。
それは元のコードが'use strict'なことです。
問題には'use strict'が条件とは書いてないのでこれでも良いのかわかりませんが、callee()はstrictモードでは使用できません。問題は元のコードを無名関数にせよということなのでstrictも考慮することにしました。
やり方がわからないので調べたらWikipwdiaにそのもののコードがありました。ただし、WikipediaのZコンビネータは名前付きの関数になっているので、それも無名関数にしています。ついでに'use strict'も追加しています。
ただし、このZコンビネータですが全然わかりません。ぱっと見、わかるのはfunctionを返す関数だということだけです。しかし、複雑すぎてどういう関数が返るのかよくわかりません。
デバッガで追いかけると、nの値はZコンビネータに適用されて10,9,…,2,1,0となりながら実行されており、確かに再帰呼び出しされています。
でも、よくわかりません。
このZコンピネータ、ナチュラルにわかる人っているんでしょうか? いるんだろうな。
私にはカリー化の概念を使って、入力された無名関数(function(n) { return n == 0 ? 1 : n * f(n - 1); })にn-1〜0までを順次適用しながら部分関数を順次返して行ってるのだろうという想像くらいしかできません。