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 の代わりに、インデックス機構を使います。

予定外に、長くなってきたので、リストに対する操作 からは、次回に。

以上、如何でしょうか?