4.1. 練習 - オブジェクト指向を使わない方法

構造体からインスタンスへの移行と合わせて、関数をメソッドに移行させてみます。

describe (引数のオブジェクトに関する情報を表示する関数)を単純に実装した例を次に示します。

CL-USER 53 > (defun my-describe (thing)
               (typecase thing
                 (cons   (describe-cons thing))
                 (symbol (describe-symbol thing))
                 (array  (describe-array thing))
                 (number (describe-number thing))
                 ;; [ その他大勢 ]
                 (t      (describe-whatever thing))))
MY-DESCRIBE
 
CL-USER 54 > (defun describe-symbol (symbol)
               (let ((package (symbol-package symbol))
                     (boundp (boundp symbol)))
                 (format t
                         "~s is a symbol. ~
      It ~:[~*does not have a home~;is in the ~s~] package. ~
      Its value is ~:[unbound~;~s~]."
                         symbol
                         package (when package (package-name package))
                         boundp (when boundp (symbol-value symbol)))))
DESCRIBE-SYMBOL
 
CL-USER 55 > (my-describe :foo)
:FOO is a symbol. It is in the "KEYWORD" package. Its value is :FOO.
NIL
 
CL-USER 56 > (my-describe '#:foo)
#:FOO is a symbol. It does not have a home package. Its value is unbound.
NIL
 
CL-USER 57 >

このコードにはいくつか問題があります。

  • typecase には効率を追求する義務がありません。もちろん my-describe ではたいして問題になりません。my-describe は対話モードで一度実行するだけですし、速くなくても誰も気にしません。しかし、Lispでは値の型によって処理を分けるのはよくあることで(GUIアプリケーションなど)、その処理が複雑になることも多く、typecase を使うには条件分岐リストの負荷が高過ぎます。
  • 文の順序に気をつけなくてはいけません。このコードでは、nullsymbol として判断されてしまいます。
  • 複数の値の型の組み合わせで分岐するとしたら、こんな使い方はどうでしょう?
(typecase (cons thing stream)
  ((cons array non-scrollable-io)
   (describe-array-non-scrollable array stream))
  ((cons array scrollable-io)
   (describe-array-scrollable array stream))
  ((cons array output-stream)
   (describe-array-general-stream array stream))
  ...)
  • もっと条件分岐を詰めれば、my-describe の定義はさらに長くなります。多数の条件分岐節を追加することになるでしょう。
  • describe-symbol などの)補助関数の名前が長くなる恐れがあります。複数の値の組み合わせも考慮することになれば、コードはすぐに読みにくくなります。


4.2. defmethod マクロ