あどけない話

インターネットに関する技術的な話など

JavaScript とλ式

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]

Schemeλ式と同じ結果になりました!

ここまで書いておいて何ですが、講演で説明できるほど優しくはないですね。^^;

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 とは違う結果になりました。

Java

Java には詳しくないので、間違っている可能性もありますが、Java ではメソッドへの参照を返すことができないので、そもそもこんなプログラムを素直に書くことは難しいのではないでしょうか?

JavaScript は C の皮を被った Lisp

ここまで読むと、JavaScriptJava よりも Scheme に近いことが納得して頂けると思います。Crockford先生は、いみじくもこう語っています。

JavaScript は、C や Java よりも、関数型言語である LispScheme に近いのです。JavaScript は、リストの代わりに配列を持ち、属性リストの代わりにオブジェクト(訳注 つまり連想配列)を持っています。

僕が思うに、JavaScriptLisp は、以下のような共通点があります。

  • Lisp では、関数もリストである。JavaScript では、関数もオブジェクトである。
  • 無名関数がある
  • 無名関数を変数に代入できる
  • 関数の中で関数を定義できる
  • クロージャがある

最後に、山本由美子さんの言葉を引用して、今日の日記を終わるのがいいでしょう。

JavaJavaScript は、グレープとグレープフルーツぐらい違います。

追記 - 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]
    }
}