ITコンサルの日常

ITコンサル会社に勤務する普通のITエンジニアの日常です。

「Ansi Common Lisp」10章読了

マクロに関する章。「On Lisp」のメインテーマだったような。これが理解できれば「On Lisp」は読みこなせるかも!?

Eval

リストをLispコードとして扱わせる。

* '(+ 1 2 3)

(+ 1 2 3)
* (eval '(+ 1 2 3))

6
* 

evalは、

  • 効率が悪い(その都度interpriteするか、コンパイルする必要がある)
  • 式はレキシカルコンテキストなしに評価される

の理由により、あまりよい方法ではない。
つまり、マクロを使えということらしい。


coerceやcompileを使って、ラムダを実行時にコードへ変換する。

* (setf aaa (coerce '(lambda (x) (* x 2)) 'function))
Warning:  Declaring AAA special.

#<Interpreted Function (LAMBDA (X) (* X 2)) {580174B9}>
* (aaa 4)
; 

; Warning: This function is undefined:
;   AAA

Error in KERNEL:%COERCE-TO-FUNCTION:  the function AAA is undefined.
   [Condition of type UNDEFINED-FUNCTION]

Restarts:
  0: [ABORT] Return to Top-Level.

Debug  (type H for help)

(KERNEL:%COERCE-TO-FUNCTION AAA)
Source: Error finding source: 
Error in function DEBUG::GET-FILE-TOP-LEVEL-FORM:  Source file no longer exists:
  target:code/fdefinition.lisp.
0] Q

* #'aaa


Error in FDEFINITION:  the function AAA is undefined.
   [Condition of type UNDEFINED-FUNCTION]

Restarts:
  0: [ABORT] Return to Top-Level.

Debug  (type H for help)

(FDEFINITION AAA)
Source: Error finding source: 
Error in function DEBUG::GET-FILE-TOP-LEVEL-FORM:  Source file no longer exists:
  target:code/fdefinition.lisp.
0] Q

* (aaa)


Error in KERNEL:%COERCE-TO-FUNCTION:  the function AAA is undefined.
   [Condition of type UNDEFINED-FUNCTION]

Restarts:
  0: [ABORT] Return to Top-Level.

Debug  (type H for help)

(KERNEL:%COERCE-TO-FUNCTION AAA)
Source: Error finding source: 
Error in function DEBUG::GET-FILE-TOP-LEVEL-FORM:  Source file no longer exists:
  target:code/fdefinition.lisp.
0] Q

* aaa
; 
#<Interpreted Function (LAMBDA (X) (* X 2)) {580174B9}>
* 'aaa

AAA
* #'+      

#<Function + {10127409}>
* #'aaa


Error in FDEFINITION:  the function AAA is undefined.
   [Condition of type UNDEFINED-FUNCTION]

Restarts:
  0: [ABORT] Return to Top-Level.

Debug  (type H for help)

(FDEFINITION AAA)
Source: Error finding source: 
Error in function DEBUG::GET-FILE-TOP-LEVEL-FORM:  Source file no longer exists:
  target:code/fdefinition.lisp.
0] Q

* (setf bbb (compile nil '(lambda (x) (* x 2))))

Warning:  Declaring BBB special.
; Compiling LAMBDA (X): 
; Compiling Top-Level Form: 

#<Function "LAMBDA (X)" {58047831}>
* bbb

#<Function "LAMBDA (X)" {58047831}>
* (funcall aaa 1)

2
* (apply aaa '(2))

4
* (funcall bbb 2)

4
* (apply bbb '(5))

10
* 

関数作れたのはいいが、どうやって呼ぶんだ? と色々試行錯誤してみたの図。
funcallとかapplyを使えばよかったのですね。

マクロ

引数をnilにするマクロ

とりあえず写経。

* (defmacro nil! (x)
    (list 'setf x nil))

NIL!
* (setf x 10)
Warning:  Declaring X special.

10
* x

10
* (nil! x)

NIL
* x

NIL
* 

なるほど、できてるっぽい。要はコードとなるリストを返す関数なのかな?

マクロを展開

macroexpand-1を使う。

* (macroexpand-1 '(nil! x))
; 
(SETF X NIL)
T
* 

 マクロ理解のポイントは、それがどのように実装されているかを理解することである。実質的にマクロは式を変換する関数にすぎない。

だそうです。

バッククォート

バッククォートの長所は、バッククォートつきの式の中で、,(コンマ)および,@(コンマ・アットマーク)を使って、評価を部分的に実行できることである。

とりあえず写経。

* (setf a 1 b 2)

Warning:  Declaring A special.
Warning:  Declaring B special.

2
* `(a is ,a and b is ,b)

(A IS 1 AND B IS 2)
*

冒頭の

入力マクロのバッククォート(逆引用符)を使うと、テンプレートからリストが作れる。

の意味がなんとなく分かったような。
コンマ・アットマークの例。

* (setf lst '(a b c))
Warning:  Declaring LST special.

(A B C)
* `(lst is ,lst)

(LST IS (A B C))
* `(its elements are ,@lst)

(ITS ELEMENTS ARE A B C)
* 

マクロ設計

  • マクロ内で使っている変数と、マクロが挿入されるコンテキストの変数と被る危険性がある
  • (反復を使った場合の)多重評価の問題

を避けるため、gemsymを使って回避する。

pprint

rubyのppに相当するものらしい。

* (pprint (macroexpand-1 '(cond (a b)
                                (c d e)
                                (t f))))

(IF A (PROGN B) (COND (C D E) (T F)))
* 

CMUCLのpprintは、インデントは付けてくれないのですね。。

一般化参照

* (defmacro my-incf (x &optional (y 1))
    `(setf ,x (+ ,x ,y)))

MY-INCF
*  (pprint (macroexpand-1 '(my-incf (car (push 1 lst))))

)

(SETF (CAR (PUSH 1 LST)) (+ (CAR (PUSH 1 LST)) 1))
* (setf lst nil)

NIL
* lst

NIL
* (my-incf (car (push 1 lst)))

2
* lst

(1 2)
* 

(push 1 lst)が二回評価されるので、二つ要素が入ってしまう。
正しくは(元々のincfを使うと)、

* (setf lst nil)

NIL
* (incf (car (push 1 lst)))

2
* lst

(2)
* 

こうらしい。
要はアトムを渡されたときは動くけど、コードリストを渡されたときのことも考えないといけないってことみたい。
難しいね。。


define-modify-macroってのを使うと、setfに関わるマクロが書きやすくなるらしい。

* (define-modify-macro our-incf (&optional (y 1)) +)

OUR-INCF
* (setf lst nil)

NIL
* (our-incf (car (push 1 lst)))

2
* lst

(2)
*