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

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

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

 第10章 マクロのその他の落し穴 です。

 評価の回数 は Common Lisp では、検討が必要ですが、newLISP では、ほとんど必要ありません。なぜなら、すでに紹介したスタイルでマクロを書けば、newLISP のマクロで引数を評価するのは、letex 内だけになるから。Common Lisp のように、ループの中で繰り返し引数が評価される展開式を意図せず書くことにはならないでしょう(笑)。それでも、“On Lisp”本書にある マクロの引数は式であって値ではないことを忘れてはいけない は、newLISP にとっても、忘れてはいけないことです。

 評価の順番 は、newLISP でも左から右へ順に評価されます。

> (setq x 10)
10
> (+ (setq x 3) x)
6
> (let ((x 1)) (for (i x (setq x 13)) (print i)))
1234567891011121313

 上記例の for は、newLISP組込です。 もちろん、適切に定義されています(笑)。

 関数によらないマクロの展開 にあるような意図せず周囲に影響を与える(副作用のこと)ようなマクロは、newLISP では、書きにくいものです。newLISP 組込関数で破壊的な関数は、20あまり。そして、Common Lisp の nconc みたい使える破壊的関数extend が破壊するのは、第一引数のみです。
 また、マクロは破壊的になっても、それを使う関数が、破壊的になることは、まれです。例えば、newLISP組込reverse は、破壊的関数です。

(defun test-f (lst)
  (reverse lst))

(define-macro (test-m)
; (test-m lst)
  (letex (_lst (args 0))
    (reverse _lst)))

 これらの動作は、

> (setq x (sequence 1 10))
(1 2 3 4 5 6 7 8 9 10)
> (test-f x)
(10 9 8 7 6 5 4 3 2 1)
> x
(1 2 3 4 5 6 7 8 9 10)
> (test-m x)
(10 9 8 7 6 5 4 3 2 1)
> x
(10 9 8 7 6 5 4 3 2 1)

 このように、マクロは破壊的ですが、関数は非破壊になります。これは、関数の場合、引数に渡される変数が、コピーされたものだからです。そういう意味では newLISP は、“On Lisp” の意図に沿った言語なのかもしれません。
 “On Lisp”本書の apply の例も、

> (defun et-al () (extend (args) (list 'et 'al)))
(lambda () (extend (args) (list 'et 'al)))
> (et-al 'smith 'jones)
(smith jones et al)
> (setq greats '(leonardo michelangelo))
(leonardo michelangelo)
> (apply et-al greats)
(leonardo michelangelo et al)
> greats
(leonardo michelangelo)

 となります。また、&rest に対しての例でも

(define-macro (echo)
  (letex (_body (extend (args) (list 'amen)))
    '_body))

(defun foo () (echo x))

 として、実行すると、

> echo
(lambda-macro () 
 (letex (_args (args)) (extend '_args (list 'amen))))
> foo
(lambda () (echo x))
> (foo)
(x amen)
> (foo)
(x amen)

 と全く問題になりません。うっかり、自己書き換えを行うスクリプトにはならないのです。

 第10章の最後 再帰 では、nth と or の実装例から
(defun、labels、car、cdr は、newlisp-utility.lsp に定義してあります)、

(define-macro (nthd)
; (nthd n lst)
  (letex (_n (args 0)
          _lst (args 1))
    (nth-fn _n _lst))) 
(defun nth-fn (n lst)
  (if (= n 0)
    (lst 0)
    (nth-fn (- n 1) (rest lst)))) 

(define-macro (nthe)
; (nthe n lst)
  (let (_num (eval (args 0))
        _lst (eval (args 1)))
    (letex (_value
             (labels 
               ((nth-in (n lst)
                  (if (= n 0)
                      (car lst)
                    (nth-in (- n 1) (cdr lst)))))
               (nth-in _num _lst)))
      _value)))

 最初の例は、特に説明は要らないですね。二番目の例では、マクロ labels を使っています。以前、再帰のマクロを実装した時labels はふさわしくないとして、内部関数定義にdefine を使いました。今回は、あえて使っています。使えたのは、letex 内の定義で使って、そこで完了させているからです。そのため、予め let 文で引数を評価して渡しています。つまり、変数_value には、内部関数nth-in で評価された値が入ります。ここまでやると、マクロの意味があまり無いような気がしますが(笑)。

(define-macro (ora)
  (letex (_ora-body (or-expand (args)))
      _ora-body)) 
(defun or-expand (_args)
  (if (null? _args)
      nil
    (let (_sym (gensym))
      (append '(let)
              (list (list _sym  (first _args)))
              (list (list 'if _sym 
                              _sym
                            (or-expand (rest _args)))))))) 
(define-macro (orb)
  (if (null? (args))
      nil
      (letex (_sym (gensym)
              _arg (first (args))
              _recv (cons 'orb (rest (args))))
        (let (_sym _arg)
          (if _sym
              _sym
              _recv)))))

 マクロora では、補助関数or-expand が、展開式を返すだけでから、

(define-macro (ora)
  (or-expand (args)))

 とすると(i+ は、newlisp-utility.lsp に定義してあります)、

> (ora nil '() (i+ 1))
(let (gensym28 nil) 
 (if gensym28 
  gensym28 
  (let (gensym29 '()) 
   (if gensym29 
    gensym29 
    (let (gensym30 (+ 1 1)) 
     (if gensym30 
      gensym30 nil))))))

 こんな感じで、展開式が出てくるだけです。それ故に letex文が必須です。
 マクロora では、評価する前に全ての評価対象が展開されますが、マクロorb は、展開式の評価の際、再び、マクロorb が呼び出されます。効率的には、評価対象が真になった時点で展開が終わるマクロorb の方が有利です。

 第10章 マクロのその他の落し穴 のまとめです。

  • newLISP の式の評価は、左から右へ順に評価されます。
  • newLISP では、意図しない副作用をもたらすマクロは、書きにくい。

 以上、如何でしょうか?

広告