HOME | 1. テンプレートの使い方と展開形の確認 | Common Lisp | 3. 構文生成マクロ | 書き込む |
変数をある数から決まったステップで増加させて、ある数に達したら処理を 終了するマクロがあると便利です。dotimes は 0 からしかはじめられませんし、 いちいち do を書くのは面倒です。そこで、マクロ for を定義するわけですが、 まず最初は変数が1ずつ増加するバージョンを作ります。
(defmacro for ((var start stop) &body body)
`(do ((,var ,start (1+ ,var))) ((>= ,var ,stop)) ; (1) wrong
,@body))
さて、定義 (1) では stop がループが繰り返すたびに評価されています。
for の引数として式が与えられることも充分考えられますので、次の場合には
不都合を生じます。
(for (i 1 (long-calculation-taking-10-seconds)) ;example (1-1) (do-something i)) (let ((j 10)) ;example (1-2) (for (i 1 (incf j)) (do-something i j)))example (1-1) ではループが回るたびに不必要に 10 秒かかってしまいますし、 example (1-2) ではループは終了しません。 これは (1) の仮引数 stop が複数回評価されたことによるものです。
さて、多重評価を避けるために次のように改良したとしましょう。
(defmacro for ((var start stop) &body body)
`(do ((,var ,start (1+ ,var)) (limit ,stop)) ((>= ,var limit)) ; (2) wrong
,@body))
(2) では多重評価を避けるために ,stop を limit に代入しました。これで、
多重評価は避けられましたが、変数が衝突する危険があります。
次の場合、ループはいきなり終了してしまいます。
(for (limit 1 10) (do-something limit))意図しない変数の補足を避けるためには、gensym-symbol を使うのが 簡単で、確実です。for の正しい定義は (3) の様になります。
(defmacro for ((var start stop) &body body) ; (3) correct
(let ((gstop (gensym)))
`(do ((,var ,start (1+ ,var)) (,gstop ,stop)) ((>= ,var ,gstop))
,@body)))
これですと、引数の多重評価も起こりませんし、変数の捕捉も起こりません。
定義 (3) の2行目は展開形に現れず、3,4行目のみが展開形に現れます。
(pme (for (i 2 10) (princ i) (terpri))) [ctrl-j]
(do ((i 2 (1+ i))
(#:G6 10))
((>= i #:G6))
(princ i)
(terpri))
ちなみに、for の仮引数が、((var start stop) &body body) の形をしていて、
var, start, stop が括弧でくくられています。これは、構造化代入といって、
defmacro では任意の構造の仮引数をとることが出来ます。
(defmacro for ((var start stop &optional step) &body body) ; (4)
(and step (not (numberp step)) (error "step should be a number in \'for\'"))
(and step (zerop step) (error "step should not be zero"))
(let ((gstop (gensym)))
`(do ((,var ,start ,(if step `(+ ,var ,step) `(1+ ,var))) (,gstop ,stop))
(,(if (or (not step) (plusp step)) `(>= ,var ,gstop) `(<= ,var ,gstop)))
,@body)))
"(if" の前にコンマがついていることに注目してください。これによって、step の
有無、正負によって生成するコードを
制御することが出来ます。また、step が数でなかったり、0 だったりするとコンパイル時に
エラーが生ずるようにしてあります。
(defmacro v-ip (v1 v2)
(_with-gensyms (k len1 len2 s)
`(let ((,len1 ,(if (consp v1) `(array-dimension ,(car v1) ,(1- (position '_ v1))) `(length ,v1)))
(,len2 ,(if (consp v2) `(array-dimension ,(car v2) ,(1- (position '_ v2))) `(length ,v2)))
(,s 0))
(or (= ,len1 ,len2) (error "length different"))
(dotimes (,k ,len1 ,s)
(incf ,s (* ,(v-ip-fn v1 k) ,(v-ip-fn v2 k)))))))
;;; make `(aref ....) used in v-ip
(defun v-ip-fn (v0 k)
(if (consp v0)
(let ((p (position '_ v0)))
`(aref ,@(subseq v0 0 p) ,k ,@(subseq v0 (1+ p))))
`(aref ,v0 ,k)))
不明な点、不正確な点などがありましたら紫藤まで お知らせいただけたら幸いです。(shido_takafumi@ybb.ne.jp)
HOME | 1. テンプレートの使い方と展開形の確認 | Common Lisp | 3. 構文生成マクロ | 書き込む |