Hatena::Groupcadr

わだばLisperになる このページをアンテナに追加 RSSフィード

2004 | 12 |
2005 | 01 | 02 | 07 | 10 | 11 |
2006 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 11 |

2008-02-13

LET、LAMBDA、PROGN色々

| 17:11 | LET、LAMBDA、PROGN色々 - わだばLisperになる を含むブックマーク はてなブックマーク - LET、LAMBDA、PROGN色々 - わだばLisperになる

毎度、誰も興味を持たないであろうというLISPについての非常に局所的な何の役にも立たない考察をしておりますが、今回は、ローカルなブロックと変数の束縛機構の変遷について調べたことを書いてみたいと思います。

現在では、大概のところはLETで、他は、用途に応じて他はPROGNを使用する、位の感じで落ち着いていると思いますが、この流れに辿り着くまでには、それなりに変遷がございました。

とりあえず、無駄に年表的に纏めてみました。

処理系機構備考
1962Lisp 1.5PROG LAMBDA PROG2
1966PDP-6 LISPPROG2拡張引数は7つ迄の制限
1973MACLISPPROGN導入
1973MACLISP3番目のDO形式導入
1977-1979Zetalispラムダリストパラメータ&aux導入
Zetalisplet導入lambdaのシンタックスシュガー
Zetalispprog拡張 prog*導入
Zetalispprog1導入
Zetalispクロージャ導入
1983Zetalisp/CLtagbody、block導入

とりあえず、上から順に追って行くと、最初は、PROGとLAMBDAとPROG2しかありませんでした。

当初LAMBDAは、ボディ部が暗黙のPROGNではないので、式は1つしかとれず、PROG2は式が2つ取れて最後(当然2番目)の値を返してました。現在は、2番目の式の値を返すという解釈がされてますが、式が2つ取れるよ、という意味だったのかもしれません。

この時点で、ローカル変数の束縛と順次進行のブロックにおいて使い勝手が良いのは、PROGだけという感じなので殆どの関数にPROGが使われてる感じです。

;; やたらとPROG時代 - みんなこんな感じ
(defun fib (n)
    (prog (a1 a2 tem)
	  (setq a1 1)
	  (setq a2 0)
       L  (cond ((< n 2) (return a1)))
	  (setq tem a1)
	  (setq a1 (+ tem a2))
	  (setq a2 tem)
	  (setq n (1- n))
	  (go L)))

それで、1966年頃、PDP-6 LISPになって、PROG2が7つまでの式を取れるように拡張されました。互換性のためか2番目の値を返すのはそのまま。UCI LISP等は、LAMBDAのボディが暗黙のPROGNになり、今のLETの用途で、LAMBDAも使われ始めました。

;; 変数束縛にLAMBDA - 1970年台後半位まで
(defun foo (lst)
  ((lambda (x y) 
     (list x y)
     ...
     ...)
   (car lst) (cdr lst)))

その後、1973年にPROGNが登場。割と遅いです。そして、3番目の形式のDOですが、

(defun foo (lst)
  (do ((x (car lst)) (y (cdr lst))) nil
    (list x y)))

のようなもので、今のLETと同じ目的で導入されました。余計なnilが付いてることを除けば、形はLETと全く同じです。

しかし、殆ど使ってるコードがないので、流行らなかった様子です。まだまだ、PROG使用率が高い。

そして、1977頃からLispマシン登場近辺で一気に色んなものが花開いちゃってるんですが、ラムダリストパラメータの&AUX、LET、PROG1、PROG*等が導入されています。

どうも、LETより、&AUXの登場の方が古いのか、初期のLispマシンのコードでは、

(defun foo (lst)
  (let ((x (car lst)) (y (cdr lst)))
    (list x y)))

よりも

(defun foo (lst &aux (x (car lst)) (y (cdr lst)))
  (list x y))

の方が多かったりします。

当時のLETは今のSchemeと同じくマクロで、LAMBDAに展開されていました。

また、LETが浸透するにつれ&AUXの存在意義も薄れて行った感じです。

それと、MACLISPでは、LETは、destructuring-bindのように分割代入可能でした。

PROGの拡張については、

(prog ((x 0) (y 1))
      (list x y))

のように初期値が設定できるようになり、LET*からの類推からかPROG*も登場。

そして、PROG1登場。それ以前は、2つの値の交換は、

(setq x (prog2 nil y (setq y x)))

のように書かれてましたが、素直に

(setq x (prog1 y (setq y x)))

と書けるようになり、この段階でPROG2は何のために存在するのか分からないものになった模様。ちなみにPSETQの登場は、もう少し後になります。

また、LETや、LAMBDAのクロージャ的用法ですが、Zetalispもダイナミックスコープなので、レキシカルスコープのように、そのままではクロージャにならないので、それ専門のCLOSUREという構文がありました。

プリミティブとしては、CLOSUREを使うのですが、EMACSのlexical-letのようなLET-CLOSEDというマクロがあり、これを使うと

(let-closed ((a 5) b (c 'x))
   (function (lambda () ...)))

という風にクロージャが作れました。Zetalispのクロージャについては、色々操作できる謎の関数が沢山ありclosure-variablesで、中身の変数を取得したり、set-in-closureで外から中身の値を設定したり、closure-functionで、関数を取り出したり、copy-closureで複製できたり、意味なく色んなことができました。

;; Lisp Machine Lisp / Zetalisp
(defun make-accumulator (init)
  (let-closed ((acc init))
    #'(lambda (n)
	(setq acc (+ acc n)))))

(setq A (make-accumulator 5.))
(funcall A 10.)
=> 15.

(closure-variables A)
=> acc

(closure-function A)
=> (lambda (n) (setq acc (+ acc n)))

;; クロージャの中の変数Aの値を取得
(symeval-in-closure A 'acc)
=> 15

;; クロージャの中身を書き換え
(set-in-closure A 'acc 100.)
=> 100.

;; 変わっちゃいました
(funcall A 10.)
=> 110.

そして、Common Lispが最初なのか、Zetalispが最初なのか良くわかりませんが、1983年頃になると、TAGBODYと、BLOCKが登場し、基本的な構文は、LETとTAGBODYとBLOCKの組み合わせで表現するという流れになって行き、PROGを使う意義も薄れ、現在に至ります。

…という風に、お馴染のLETに辿り着くまでに割と色々あったということが分かったような、分からないような…。

以上、何のまとまりもないんですが、ローカルブロックの変遷でした。