amachangさんの講演資料を観ていて、「ラムダ式も出来る」というページがありました。これに触発されて、少し書いてみます。
amachang さんが挙げていらっしゃる例は「λx.x」で、JavaScript で書くとこうなります。
function(x) { return x; }
とても面白い話を示唆しているのですが、この単純な例だと、聴講者に「それなら C/Java でも書けるよ」という印象を持たれてしまいそうです。
そこで、JavaScript では表現できて、C/Java ではできない例を挙げてみようと思います。
funarg 問題
言語がλ式をエミュレートするためには、厳密な条件があるらしいのですが、その一つがクロージャです。クロージャを使った例を使えば、JavaScript ではできて、C/Java ではできないことが示せるでしょう。
私の上司である和田先生に聞いたところ、「そういうのは funarg 問題と言う」のだそうです。関数を引数に取る問題と言う意味らしいです。
Scheme
和田先生が教えてくれた Scheme の例はこうです。
(let ((y 2)) (((lambda(y) (lambda(x) (list x y))) 1) 0)) ;; => (0 1)
分かりますか?理解しやすいように補助変数を導入してみましょう。
(let* ((y 2) (func (lambda(y) (lambda(x) (list x y))))) ((func 1) 0)) ;; => (0 1)
func は、関数を返す関数です。(func 1) を実行して返ってくる関数は、
(lambda (x) (list x y))
ですね。外側の関数の y に 1 が与えられており、これがクロージャとなるため、y は 1 になります。
なので、最終的な値は (0 1) となります。
Emacs Lisp
違いを分かって頂くために、Emacs Lisp でもやってみましょう。Emacs Lisp では、変数と関数の名前空間が分かれているので、funcall を使わないといけません。
(let* ((y 2) (func (lambda(y) (lambda(x) (list x y))))) (funcall (funcall func 1) 0)) ;; => (0 2)
Emacs Lisp は動的スコープです。クロージャには静的スコープが必須であり、従って Emacs Lisp にはクロージャがありません。
(funcall func 1) が返す関数は、Scheme と同様に
(lambda (x) (list x y))
ですが、この y は次の実行時まで値が決まりません。ですから、次の評価は以下のコードを同じになります。
(let* ((y 2)) (funcall (lambda (x) (list x y)) 0)) ;; => (0 2)
だから、最終的な値が (0 2) になる訳です。
λ式
僕の友達の小田ちゃんに、この例をλ式で計算してもらいました。
(λy.((λy.(λx.(list x y))) 1) 0) 2 ((λy.(λx.(list x y))) 1) 0 (λx.(list x 1)) 0 (list 0 1)
Scheme と同じ結果になっています。
JavaScript
いよいよ JavaScript の例です。
var y = 2; ((function(y) {return function(x){ return [x, y]; }})(1))(0); // => [0, 1]
ここまで書いておいて何ですが、講演で説明できるほど優しくはないですね。^^;
C
C でも無理矢理書いてみました。
#include <stdio.h> typedef int *(*funcptr)(); funcptr lambda(); int *lambda2(); int y = 2; int ret[2]; funcptr lambda(int y) { return lambda2; } int *lambda2(int x) { ret[0] = x; ret[1] = y; return ret; } main () { (*(lambda(1)))(0); printf("[%d, %d]\n", ret[0], ret[1]); } /* => [0, 2] */
Scheme/λ式/JavaScript とは違う結果になりました。
JavaScript は C の皮を被った Lisp
ここまで読むと、JavaScript は Java よりも Scheme に近いことが納得して頂けると思います。Crockford先生は、いみじくもこう語っています。
JavaScript は、C や Java よりも、関数型言語である Lisp や Scheme に近いのです。JavaScript は、リストの代わりに配列を持ち、属性リストの代わりにオブジェクト(訳注 つまり連想配列)を持っています。
僕が思うに、JavaScript と Lisp は、以下のような共通点があります。
- Lisp では、関数もリストである。JavaScript では、関数もオブジェクトである。
- 無名関数がある
- 無名関数を変数に代入できる
- 関数の中で関数を定義できる
- クロージャがある
最後に、山本由美子さんの言葉を引用して、今日の日記を終わるのがいいでしょう。
Java と JavaScript は、グレープとグレープフルーツぐらい違います。
追記 - Java
以下、みずしまさんがコメントに書いて下さった Java のコードです。コメントでは見にくいので、こちらに移しました。
import java.util.*; interface Fn<A,B> { B apply(A arg); } public class Example { public static void main(String[] args) { final int y = 2; List<Integer> result = new Fn<Integer, Fn<Integer, List<Integer>>>() { public Fn<Integer, List<Integer>> apply(final Integer y) { return new Fn<Integer, List<Integer>>() { public List<Integer> apply(final Integer x) { return Arrays.asList(x, y); } }; } }.apply(1).apply(0); System.out.println(result); // => [0, 1] } }