Archive for 2010年6月24日|Daily archive page
newLISP で On Lisp する...第4章(その1)
(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。)
今回から、第4章 ユーティリティ関数 です。この章は、関数例が多いの長丁場です。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsp に定義してあります。
まずは、ユーティリティの誕生 から。
関数all-nicknames に使われている nconc は、newLISP にはありませんが、第3章(その1)で定義しました。
しかし、ここでは、組込extend の代用で十分です。
(define nconc extend) (defun all-nicknames (names) (if (null names) '() (nconc (nicknames (car names)) (all-nicknames (cdr names)))))
nconc(または、extend) の代わりに append を使っても動作は同じです。
“On Lisp”本書でも後に マッピング で解説していますが、副作用の効果を狙った使い方でないからです。
newLISP では、nil と '() は同じではありません。
ここでの組込extend の引数はリストなので、空リストの '() が必要になります(組込append の場合も同様)。
また、次の例の mapcan も newLISP には、ありません。そこで、mapcan を定義して、mapcar(newLISP では map) との違いを見ていきましょう。
(defun mapcan () (apply extend (apply map (args))))
この定義自体も、後に マッピング で出てくる定義の newLISP版です。
ただし、度々書いていますが、newLISP では、副作用を持つ関数は書けません。
副作用まで記述するならマクロにすべきです。
その場合は、
(define-macro (mapcan) (letex (_body (cons 'map (args))) (apply extend _body)))
となります。
しかし、Common Lispのような副作用は出ません。後に マッピング で出てくる mappend と同じ動作になります。
例えば、Common Lisp なら
[1]> (let ((x '((a) 1)) (y '((b) 2)) (z '((c) 3))) (mapcan #'car (list x y z))) (A B C) [2]> (let ((x '((a) 1)) (y '((b) 2)) (z '((c) 3))) (mapcan #'car (list x y z)) x ) ((A B C) 1)
となりますが、newLISP では、
> mapcan (lambda-macro () (letex (_body (cons 'map (args))) (apply extend _body))) > (let (x '((a) 1) y '((b) 2) z '((c) 3)) (mapcan first (list x y z))) (a b c) > (let (x '((a) 1) y '((b) 2) z '((c) 3)) (mapcan first (list x y z)) x) ((a) 1) > (defun mapcan () (apply extend (apply map (args)))) (lambda () (apply extend (apply map (args)))) > (let (x '((a) 1) y '((b) 2) z '((c) 3)) (mapcan first (list x y z))) (a b c) > (let (x '((a) 1) y '((b) 2) z '((c) 3)) (mapcan first (list x y z)) x) ((a) 1)
といった具合に、関数で定義しても、マクロで定義しても、同じ動作になります。
newLISP で定義した nconc のようには副作用まで再現できませんでした(汗)。
何故か?それはまた、別の機会に。
ということで、この章の使い方では、関数版mapcan で十分なのです。
さて、話しを戻して、動作を見るために、関数nicknames を定義します。
> (defun nicknames (name) (rest (parse name)))
定義した関数nicknames は、セカンドネームのリストを返します。
最初は、mapcar の例。newLISP では、map を使います。
> (map nicknames '("a 1" "b 2" "c 3")) (("1") ("2") ("3"))
newLISPでは、引数の関数に #’ を付加する必要がないことに注意して下さい。
各要素毎にリストが作られ、そのリストが返されます。
中の括弧が邪魔? それなら関数all-nicknames を作ればいい。
> (all-nicknames '("a 1" "b 2" "c 3")) ("1" "2" "3")
中の括弧が取り払われます。
でも、mapcan を知っている人は、関数all-nicknames なんかを作らない。
> (mapcan nicknames '("a 1" "b 2" "c 3")) ("1" "2" "3")
“既知の関数を知っているか、いないかは、大きな違いだけれど、何がほしいかが分かっていることはもっと重要” というのが、ここのポイント。
とはいえ、組込関数が高々350個くらいという、newLISP の方が組込関数を知っている点では有利かも(笑)。
上記の結果がほしい時、newLISP では、どうするか?
> (apply extend (map nicknames '("a 1" "b 2" "c 3"))) ("1" "2" "3")
あるいは、この場合のように、結果に入れ子の括弧が無いなら、
> (flat (map nicknames '("a 1" "b 2" "c 3"))) ("1" "2" "3")
といったところ。この程度なら、mapcan も nconc も要らない?
さて、次の例題で使われている find-if も newlISP には、ありません。
嘘です、あります。&key オプションを除けば、組込exists が find-if 相当です。
これで、例題を記述すると、
(define find-if exists) (let ((town (find-if bookshops town))) (values town (bookshops town)) (defun find-books (towns) (if (null towns) nil (let ((shops (bookshops (car towns)))) (if shops (values (car towns) shops) (find-books f (cdr towns))))))
values の定義は、こちらにあります。そして、本命の関数find2 は、
(defun find2 (f lst) (if (null lst) nil (let ((val (f (car lst)))) (if val (values (car lst) val) (find2 f (cdr lst))))))
となります。newLISP では、funcall が要らないことに注意して下さい。また、fn は f にしてあります。
さて、実際に動きを見てみましょう。本屋の代わりにリストの中から数字を探します。
> (defun bookshops (lst) (find-if numberp lst)) (lambda (lst) (find-if numberp lst)) > (setq towns '((city0 a b c)(city1 a 2 c)(city2 1 b c)(city3 a b 3))) ((city0 a b c) (city1 a 2 c) (city2 1 b c) (city3 a b 3)) > (let ((town (find-if bookshops towns))) (values town (bookshops town))) (city1 a 2 c) > (multiple-value-list (let ((town (find-if bookshops towns))) (values town (bookshops town)))) ((city1 a 2 c) 2) > (find2 bookshops towns) (city1 a 2 c) > (multiple-value-bind (town bookshop) (find2 bookshops towns) (list (town 0) bookshop)) (city1 2)
find-books も find2 も所望の動作をしています。
次は、 抽象化への投資 です。
ユーティリティを作る前に、よく読んでおくことをお勧めします。
さて、特に例題はないのですが、先程の find2 を newLISP だけで書いてみましょう。
(define (find3 f lst) (and lst (letn (L (lst 0) (val (f L))) (if val (values L val) (find3 f (1 lst))))))
こんな感じでしょうか。car や cdr の代わりに、インデックス機構を使います。
予定外に、長くなってきたので、リストに対する操作 からは、次回に。
以上、如何でしょうか?