Shammer's Philosophy

My private adversaria

Lispでリフレクション第01回

Lispで、Javaのリフレクションのようなことをやりたい。具体的には、(動的な)変数から呼び出したいメソッド名を
作成して、その変数名のメソッドをそのまま実行する、というようなイメージ。そのために以下のようなコードを
書いて試してみた。

(defun method-1 ()
  (format t "This is a method-1.~%"))

(defmacro invoke-method (param &rest args)
  (let ((method-name (append-string "method-" (string param))))
    `(,(intern method-name) ,@args)))

(invoke-method "1")

予想では、最後の (invoke-method "1") の結果、method-1 が呼び出されるはず、と思っていたが、実際には以下のような感じでエラーになった。

> Error: Undefined function |method-1| called with arguments () .
> While executing: FUNCALL, in process listener(1).
> Type :GO to continue, :POP to abort, :R for a list of available restarts.
> If continued: Retry applying |method-1| to NIL.
> Type :? for other options.
1 >

余計なものがくっついている。なぜ前後に | があるのか・・・調べてみると、これは大文字小文字の区別があったり、
あるいは空白(スペース)を含むシンボルだと自動で付与されるもののようだ。実際、

  • method-1 の名前を |method-1| にする
  • 作成後のメソッド名変数を大文字に変換する

のいずれかを実施すれば、期待通りに動くことを確認。
最終的には以下のようになった。

(defun method-1 ()
  (format t "This is a method-1.~%"))

(defun method-2 ()
  (format t "This is a method-2.~%"))

(defun method-3 ()
  (format t "This is a method-3.~%"))

(defmacro invoke-method (param &rest args)
  (let ((method-name (string-upcase (append-string "method-" (string param)))))
    `(,(intern method-name) ,@args)))

(invoke-method "1")
(invoke-method "2")
(invoke-method "3")

実行結果は以下のとおり。

This is a method-1.
This is a method-2.
This is a method-3.

intern は、オプションでパッケージ名を渡して起動することができる。渡さない場合、カレントの
パッケージ名が使用される。全てカレントのパッケージで実行されるのなら問題ないかもしれないが、
パッケージを意識した場合は、少し手を加える必要があるかもしれない。