HOME | 3. 構文生成マクロ | Common Lisp | 書き込む |
(defmacro allf (val &rest args) ; (1) (let ((gval (gensym))) `(let ((,gval ,val)) (setf ,@(mapcan #'(lambda (a) (list a gval)) args))))) (defmacro nilf (&rest args) `(allf nil ,@args)) ; (2) (defmacro tf (&rest args) `(allf t ,@args)) ; (3)allf を展開すると次のようになります
(pme (allf the-same-value a b c d e)) (let ((#:G1 the-same-value)) (setf a #:G1 b #:G1 c #:G1 d #:G1 e #:G1))
(setf var (ope var)) ; (4) (defmacro setope (var) ; (5) `(setf ,var (ope ,var))) (pme (setope (aref a (incf i)))) ; (6) (setf (aref a (incf i)) (ope (aref a (incf i))))この種のマクロを書くマクロとして define-modify-macro が使えます。このマクロは次の3つの引数を取ります:
;;; C like operators (define-modify-macro ++ () 1+) ; (7) (define-modify-macro -- () 1-) ; (8) (define-modify-macro += (var) +) ; (9) (define-modify-macro -= (var) -) ; (10) (define-modify-macro *= (var) *) ; (11) (define-modify-macro /= (var) /) ; (12) ;;; use C like operators (setf a 100) [ctrl-j] 100 (++ a)[ctrl-j] 101 (-- a)[ctrl-j] 100 (/= a 5)[ctrl-j] 20 (*= a 10)[ctrl-j] 200 (+= a 200)[ctrl-j] 400 | (-= a 300)[ctrl-j] 100 ;;; apply to a hash table (setf h (make-hash-table))[ctrl-j] #<hashtable 46863060> (setf (gethash 'k h) 100)[ctrl-j] 100 (++ (gethash 'k h))[ctrl-j] 101 (gethash 'k h)[ctrl-j] 101 t ;;; apply to an array (setf ar #(10 20 30 40 50))[ctrl-j] #(10 20 30 40 50) (/= (aref ar 3) 20)[ctrl-j] 2 ar[ctrl-j] #(10 20 30 2 50) |
(define-modify-macro rotc (phi) ; (13) (lambda (c0 phi) (* c0 (exp (* #C(0 1) phi))))) ;;; use rotc (setq a 1)[ctrl-j] 1 (rotc a (/ pi 4))[ctrl-j] #C(0.7071067811865476d0 0.7071067811865475d0) (rotc a (/ pi 4))[ctrl-j] #C(2.220446049250313d-16 1.0d0) a[ctrl-j] #C(2.220446049250313d-16 1.0d0)define-modify-macro で定義できるのは 「場所」が特定でき、変更された値を返すマクロのみです。 従って、push や pop はdefine-modify-macro では定義できません。 (push は変更するのが特定の「場所」ではなく、 pop は返り値が変更されたオブジェクトではないから。) push や pop を定義するには次の節で述べる get-setf-expansion を使います。
get-setf-expansion は「場所」の値を参照したり代入したりする方法を返します。処理系によって これらの方法は違うので、可搬性のあるプログラムを書くためには、実装に依存する方法を 直接使うことなく、get-setf-expansion の返り値を使うべきです。 式 (15)--(18) に xyzzy と clisp で 配列とハッシュ表の場合の get-setf-expansion の返り値を示します。 (15)--(18) にあるように返り値は実装により異なります。 get-setf-expansion は 次の5つの値を返します:
一般にこの種のマクロの書き方は以下の通りです。具体的なやり方は (19)--(21) を 見てください。
;;; should be defined in siteinit.l or .xyzzy (defmacro get-setf-expansion (&rest argvs) ; (14) `(get-setf-method ,@argvs)) ;;; xyzzy array (get-setf-expansion '(aref a i j))[ctrl-j] ; (15) (#:G1 #:G2 #:G3) (a i j) (#:G4) (system:*aset #:G1 #:G4 #:G2 #:G3) (aref #:G1 #:G2 #:G3) ;;; xyzzy hash table (get-setf-expansion '(gethash key *hash*))[ctrl-j] ; (16) (#:G5 #:G6) (key *hash*) (#:G7) (system:*puthash #:G5 #:G6 #:G7) (gethash #:G5 #:G6) ;;; clisp array [21]> (get-setf-expansion '(aref a i j))[Return] ; (17) (#:G2091 #:G2092 #:G2093) ; (A I J) ; (#:G2094) ; (SYSTEM::STORE #:G2091 #:G2092 #:G2093 #:G2094) ; (AREF #:G2091 #:G2092 #:G2093) ;;; clisp hash table [22]> (get-setf-expansion '(gethash key *hash*))[Return] ; (18) (#:G2095 #:G2096) ; (KEY *HASH*) ; (#:G2097) ; (SYSTEM::PUTHASH #:G2095 #:G2096 #:G2097) ; (GETHASH #:G2095 #:G2096) ;;; our push (defmacro our-push (obj place) ; (19) (multiple-value-bind (vars argvs val setter accessor) (get-setf-expansion place) `(let ,(mapcar #'list vars argvs) (let ((,@val (cons ,obj ,accessor))) ,setter)))) ;;; our pop (defmacro our-pop (place) ; (20) (let ((gval (gensym))) (multiple-value-bind (vars argvs val setter accessor) (get-setf-expansion place) `(let ,(mapcar #'list vars argvs) (let ((,gval (car ,accessor)) (,@val (cdr ,accessor))) ,setter ,gval))))) ;;;; (defmacro popn (n place) ; (21) (let ((gval (gensym)) (gn (gensym))) (multiple-value-bind (vars argvs val setter accessor) (get-setf-expansion place) `(let ,(mapcar #'list vars argvs) (let ((,gn ,n)) (let ((,gval (subseq ,accessor 0 ,gn)) (,@val (nthcdr ,gn ,accessor))) ,setter ,gval))))))
不明な点、不正確な点などがありましたら紫藤まで お知らせいただけたら幸いです。(shido_takafumi@ybb.ne.jp)
HOME | 3. 構文生成マクロ | Common Lisp | 書き込む |