Archive for 2010年7月11日|Daily archive page

newLISP で On Lisp する...第9章

(この blog は、“short short story または 晴耕雨読な日々”からの引越してきたもの。スクリプトは、newLISP V10.2.1 以降で動作するように書き直しています。
defun 等の newLISP組込関数に無い関数は、特に断らない限り、newlisp-utility.lsp に定義してあります。)

 第9章 変数捕捉 です。

 マクロ引数の捕捉自由なシンボルの捕捉 は共に、newLISP でも起こりうる捕捉です。そして、捕捉はいつ起きるのか で定義されている、自由、骨格、捕捉可能 も、そのまま newLISP に当てはまり、ここで、気を付けなければならないのは、自由なシンボルです。

> (let ((x y) (z 10)) (list w x z))
(nil nil 10)

 上記は、newLISP の例です。Common Lisp であれば、y や w が定義されていないとエラーなるところでも、newLISPでは、エラーとならず nil が入ります。未定義のシンボルが現れると、nil 入ったシンボルとして、その時点の context に登録されます。マクロに限らず、この点を頭に入れておかないと、思わぬバグを潜ませることになります。

 そして、本章のメイン、捕捉の回避方法 ですが、適切な名前によって捕捉を避ける事前評価によって捕捉を避けるgensym によって捕捉を避けるパッケージによって捕捉を避ける、いずれも、newLISP で有効です。その中で、“On Lisp”本書でのお薦めは、gensym によって捕捉を避ける ですが、newLISP でのお薦めは、パッケージによって捕捉を避ける となります。もっとも、パッケージは、コンテキスト(context) となりますけど。“On Lisp”本書で、この方法は、一般的な解決方法にならない とされています。Common Lisp では、その通りですが、newLISP では、事情が異なります。context の default functor を使うことで、Common Lisp での問題点まで回避できるのです。つまり、

  • context の default functor を使うことで、マクロ名だけでどのcontext上からも呼び出せる。
  • 同様に、マクロ名専用の context を作るので、context 内での変数捕捉を考える必要が無い。

 context の default functor によるマクロを具体的に例で見てみましょう(defun、hayashi、i+、psetq は、newlisp-utility.lsp に定義してあります)。

(context 'MAIN:myfor)
(defun make-stepform (bindform)
  (remove nil (mappend (fn (b) (if (and (consp b) (third b))
                               (list (car b) (third b))
                                '()))
                         bindform)))
(define-macro (do)
  (letex (_init (map (hayashi slice 0 2) (args 0))
          _steps (cons 'psetq (make-stepform (args 0)))
          _results (cons 'begin (rest (args 1)))
          _end-test (first (args 1))
          _body (cons 'begin (2 (args))))
   (let _init
     (until _end-test
       _body
       _steps)
     _results)))
(define-macro (myfor:myfor)
; (for (var start stop) &body body)
  (letex (_var (args 0 0)
          _start (args 0 1)
          _stop  (args 0 2)
          _body  (cons 'begin (1 (args))))
     (do ((_var _start (i+ _var))
          (limit _stop))
       ((> _var limit) nil)
         _body)))
(context MAIN)

 newLISP には、for がありますので myfor を context の default functor としてマクロを定義しています。また、newLISPには、do がありませんので、それも context myfor 内に定義しています。どちらのマクロも、内部変数名に _body を使っていますが、変数捕捉はされません。それを動作で見てみましょう。(動作には、先に newlisp-utility.lsp を読み込んで置いた方が無難です)。

(myfor (limit 1 5) (print limit))

 普通に context を使っていないマクロであれば、このスクリプトは、止まりません(終了判定がされない)。マクロmyfor 内の終了判定に使っている変数 limit と同じ名前の変数を myfor で指定したからです。しかし、上記のように context の default functor として、myfor を定義していれば、マクロmyfor は下記のように展開されます。

(myfor:do ((limit 1 (++ limit)) (myfor:limit 5)) ((> limit myfor:limit) nil) 
 (begin 
  (print limit)))

 このように、マクロmyfor の引数limit とマクロmyfor の骨格にあるlimit は context で分けられています。同時に、マクロmyfor 内の _body は、マクロmyfor の第2引数に置き換わっています。
 さらに、do 文は、

(let ((limit 1) (myfor:limit 5)) 
 (until (> limit myfor:limit) 
  (begin 
   (begin 
    (print limit))) 
  (psetq limit (++ limit))) 
 (begin 
  nil))

 と展開されます。ここまで来れば、動作は明らか。
 これを、評価すれば、

12345nil

 となります。このように、マクロ名だけで呼び出せ、しかも、専用の名前空間なので変数捕捉が起こりえない、context の default functor は、newLISP で変数補足を起こさないマクロを書く時の、最適な手段です。欠点は、以前にも書いたように、そのマクロ名は、変数名として使えなくなること

 その他の名前空間における捕捉変数補足にこだわる理由 は、newLISP でも同じだと思います。

 ということで、第9章 変数捕捉 のまとめです。

  • newLISP での変数補足は、CommonLisp の場合とほぼ同じです。
  • newLISP での変数捕捉回避方法も、CommonLisp とほぼ同じです。
    ただし、お薦めは、context の default functor によるマクロ定義です。(“On Lisp”本書のパッケージによる回避に相当)

 以上、如何でしょうか?

広告