Docs in Common Lisp

Meta notes

対象読者

Documentation string

Common Lispには、例えば関数定義構文にドキュメンテーション文字列を挿入できる機能がある。

たとえば以下のようにする。

(defun example(x)
  "This is documentation."
  x)

ドキュメンテーション文字列は、たとえばDESCRIBE経由で参照できる。

* (describe 'example)

COMMON-LISP-USER::EXAMPLE
  [symbol]

EXAMPLE names a compiled function:
  Lambda-list: (X)
  Derived type: (FUNCTION (T) (VALUES T &OPTIONAL))
  Documentation:
    This is documentation.
  Source form:
    (LAMBDA (X) "This is documentation." (BLOCK EXAMPLE X))

DESCRIBEの欠点は出力内容が処理系依存なことである。 (なお、上記出力はSBCLのもの。)

余計な出力が必要ないならDOCUMENTATIONでドキュメンテーション文字列のみを取り出せる。

* (documentation 'example 'function)
"This is documentation."

Issue

個人的にこの機能はなかなか良い機能だと思ってはいるのだが、それがうまく機能しているかというと少々疑問がある。

そもそもエンドユーザーとしてドキュメンテーション文字列に期待するのはどのような情報だろうか?

僕が期待するものは引数の型情報、返り値の型情報、副作用があるかないか、コンディションを投げることがあるかなどであり、これは要するにHYPERSPECのような内容である。

当然それは一行やそこらで収まるものではない。 僕が見てきた中で最長のものはDRAKMA:HTTP-REQUESTのそれで、実に243行に渡る。

そのような長いドキュメンテーション文字列はソースコードを少々読みにくくさせる。 特に問題なのが、最悪の場合ラムダリストがエディタの画面外に行ってしまう点である。 (もっとも、DRAKMA:HTTP-REQUESTのラムダリストはそれだけで45行もあるのだが。)

こういった点を苦々しく思っているのは僕だけではないようで、なんとかしようと試みている例は幾つかある。

Solution 1

CL-ANNOTはアノテーションを使うことでドキュメンテーション文字列を関数定義フォームの外側へ出せるようにしている。

@doc
"This is documentation"
(defun example(x)
  x)

欠点はドキュメンテーション文字列がフォームに束縛される点である。 すなわち、別ファイルに分けることができない。

Solution 2

DOCUMENTATION-UTILSはマクロで後からドキュメンテーション文字列をSETFするようにしている。

(defun example(x)
  x)
(docs:define-docs
  (example "This is documentation"))

同様のことはINTROSPECT-ENVIRONMENTも独自のマクロで行っている。

欠点はポータビリティに欠ける点である。 総称関数DOCUMENTATIONへのSETFは言語仕様上できるはずなのだが、少なくともECLはそれに違反しておりSETFできない。

Conclusion

個人的には関数定義フォームへのドキュメンテーション文字列はASDFでいうところのショートデスクリプションであるべきなのではないかと思っている。

そして、長さが必要なら(ASDFでいうところのロングデスクリプションは)総称関数DOCUMENTATIONへの:AROUNDメソッドとして定義するのがスマートではなかろうか。

(defun example(x)
  "This is documentation title"
  x)

(defmethod documentation :around ((name (eql 'example))(type (eql 'function)))
  (format nil "~@[~A~2%~]~A"
      (call-next-method)
      "This is documentation body"))