Shammer's Philosophy

My private adversaria

関数を生成するマクロ・その1

プログラムを書いていると、どうしても同じようなパターンの関数を定義することになる。それも、アクセッサのような単調なもの。単調なくせに数が多く、これをもっとスッキリさせることができないかと思っていたが、どうやらマクロでできそうだとわかる。ということで書いてみた。

? (defmacro generate-semaphore-accessor (name)
  `(let ((semaphore (make-semaphore)))
     (defun ,name ()
       semaphore)))
GENERATE-SEMAPHORE-ACCESSOR
? (generate-semaphore-accessor 'get-my-semaphore) 
> Error: The value 'GET-MY-SEMAPHORE is not of the expected type FUNCTION-NAME.
> While executing: CCL::VALIDATE-FUNCTION-NAME, in process listener(1).
> Type :POP to abort, :R for a list of available restarts.
> Type :? for other options.
1 > q
? (generate-semaphore-accessor get-my-semaphore)
GET-MY-SEMAPHORE
? (get-my-semaphore)
#<SEMAPHORE #x302000FB5FDD>
?

セマフォを生成して返すもの。let にしているのは、Java でいう private にしたいがため。defparameter で生成されたインスタンスはアクセサを使用せずともアクセスできてしまう。どうも Javaカプセル化の考え方が染みついてしまっているらしく private なインスタンスを用意できない、というのが気持ち悪い。そのため、private な引数は let で定義するようにして、その let の有効範囲内で getter を書く。
そういう気持ちで let を使っていたが、マクロの処理を let で囲むというのはこれ以上の効果があるようだ。今回の例で言うと、すでにプログラムのどこかで semaphore というシンボルが使用されているとしたら、

(defun ,name ()
  semaphore)

は期待しているものでない、別のオブジェクトを返してくるだろう。let で囲っていることでこういうことを回避でき、別の場所で semaphore というシンボルを使用していたとしても確実に make-semaphore で生成されたセマフォが返されるようになる。しかも、このマクロを抜けたとしても(存在するかもしれない)既存の semaphore というオブジェクトへの影響も回避できる。

マクロの中には、&rest や &body で一連の処理を渡すものがある。これらの中で何かのシンボルがあっても、それはマクロとは関係ない何らかのオブジェクトを意図しているのが常だ。マクロの中で let をして、その let で定義された変数が &body や &rest の中で参照するシンボルと重なってしまうと最悪なことになる。そういう場合は gensym を使えという話になるのだろうが・・・