読者です 読者をやめる 読者になる 読者になる

レガシーコード生産ガイド

私に教えられることなら

PAIPメモ 3

CommonLisp

PAIP(実用Common Lisp)を読んでて気になって調べた事などをメモします。

今回はch3.13「デバッグツール」から。

step

SBCLと、SBCLをSLIMEから使ったところ上手くステップ実行できませんでしたが、 CLISPだとPAIPで説明されているようなステップで実行できました。 処理系で結構違いがあるみたいです。

追記: デバッガからステップ実行できるそうです。(id:nfunatoさんのコメント参照)

inspect

SmalltalkやFactorの影響でinspectorが好きになったので、CommonLispにもある (というよりSmalltalkとInterlispが発祥なのでしょうか?)のは嬉しいですね。

SBCLのinspectを試してみます。qで終了します。

* (inspect 'mapcar)

The object is a SYMBOL.
0. Name: "MAPCAR"
1. Package: #<PACKAGE "COMMON-LISP">
2. Value: "unbound"
3. Function: #<FUNCTION MAPCAR>
4. Plist: NIL
> 3

The object is a FUNCTION named MAPCAR.
0. Lambda-list: (FUNCTION LIST &REST SB-IMPL::MORE-LISTS)
1. Ftype: (FUNCTION ((OR FUNCTION SYMBOL) LIST &REST T) (VALUES LIST &OPTIONAL))
> 0

The object is a proper list of length 4.
0. 0: FUNCTION
1. 1: LIST
2. 2: &REST
3. 3: SB-IMPL::MORE-LISTS
> 0

The object is a SYMBOL.
0. Name: "FUNCTION"
1. Package: #<PACKAGE "COMMON-LISP">
2. Value: "unbound"
3. Function: #<FUNCTION FUNCTION>
4. Plist: (SB-WALKER::WALKER-TEMPLATE (NIL SB-WALKER::CALL))
> q

面白いですね。シンボルにいろいろな情報が結び付けられているのがわかります。 パッケージについての情報も役に立ちそうです。

documentation

describeで見れるドキュメンテーションストリングを取得できる関数です。

例えば(documentation 'reverse 'function)で、(describe 'reverse)で見れる解説の Documentationの部分が文字列で返されます。

また、setfで書き換えることもできます。

CL-USER> (setf (documentation 'reverse 'function)
               "ソビエトロシアでは、リストがあなたを逆にする!!")
"ソビエトロシアでは、リストがあなたを逆にする!!"

CL-USER> (describe 'reverse)
COMMON-LISP:REVERSE
  [symbol]

REVERSE names a compiled function:
  Lambda-list: (SEQUENCE)
  Declared type: (FUNCTION (SEQUENCE)
                  (VALUES
                   (OR (SIMPLE-ARRAY * (*)) CONS NULL
                       SB-KERNEL:EXTENDED-SEQUENCE)
                   &OPTIONAL))
  Documentation:
    ソビエトロシアでは、リストがあなたを逆にする!!
  Known attributes: flushable, unsafely-flushable
  Source file: SYS:SRC;CODE;SEQ.LISP
; No value

check-type

引数の型を調べるcheck-typeですが、定数を判定することはできません

CL-USER> (check-type 3 list)
; in: CHECK-TYPE 3
;     (SETF 3 (SB-KERNEL:CHECK-TYPE-ERROR '3 #:G950 'LIST NIL))
; ==>
;   (SETQ 3 (SB-KERNEL:CHECK-TYPE-ERROR '3 #:G950 'LIST NIL))
; 
; caught ERROR:
;   Variable name is not a symbol: 3.

定数をチェックする必要はないので当たり前ですね。

defclassで定義したクラスもちゃんと調べられました

CL-USER> (defclass foo () ())
#<STANDARD-CLASS FOO>
CL-USER> (defvar f1 (make-instance 'foo))
F1
CL-USER> (check-type f1 foo)
NIL

判定が否だとエラーコンディションが投げられ、成功するとNILが返ってきます。

継承もちゃんと判定できます。

CL-USER> (defclass bar (foo) ())
#<STANDARD-CLASS BAR>
CL-USER> (defvar b1 (make-instance 'bar))
B1
CL-USER> (check-type b1 foo)
NIL

ということは

  • CommonLispの型クラスに、CLOSのクラスは登録される
  • check-typeはCLOSのクラスにも対応している

のどちらかでしょう。clispのソースを見てみると、src/condition.lispでdefmacroされて、typepを使った判定コードに変換されています。

typepsrc/type.lispでdefunされていて、ソースを読むと

  • 型のシンボル、をexpand-deftypeしたもののplistからTYPE-SYMBOLTYPE-LISTDEFSTRUCT-DESCRIPTIONを取得して、あればそれを使う
  • type.lisp内で定義されているclos-classで判定する

となっていました。

expand-deftypeの定義が見つからなかったので(どこだろう??)これ以上追いませんが、とりあえず後者の「check-typeはCLOSのクラスにも対応している」のようです。データ型はdeftypeで宣言できるようですが、CLOSでは勝手には宣言しないということでしょうか。

それからWikipediaのCommonLispのページを見ると、前回のt型などの説明がしっかり書いてありました。。

Common Lisp - Wikipedia

すべての type は supertype として t (他の言語におけるtrueやObject)をもつ。従って、全てのオブジェクトは型tのインスタンスである。一方、型nilは、どのオブジェクトもそのインスタンスにならない型である。

assert

同じくWikipediaのページに

assert関数による基本的なテスト駆動開発が可能

とあります。アサーションが真になるようにテストを修正して継続するなど、面白い機能もついています。

CommonLispを触り始めの頃に試しにテストスイート書いた記憶がありますが、これを使えば簡単に書けますね。試しに書いてみます。

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun divide-tests (tests &optional (divided nil))
    ;; (arg => value arg => value) => ((arg value) (arg value))
    (if (null tests)
        (reverse divided) 
        (let ((arg   (first tests))
              (value (third tests)))
          (divide-tests
           (cdddr tests)
           (cons (list arg value) divided)))))

  (defun make-exp (f arg)
    (cons f (if (listp arg) arg (list arg))))

  (defun test-composer (f)
    (lambda (test)
      (let* ((arg   (first test))
             (value (second test))
             (exp   (make-exp f arg)))
        `(assert (equal ,exp ,value)))))
  
  (defun asserts-f-body (f tests)
    (let* ((tests (divide-tests tests))
           (composer (test-composer f)))
      (cons 'progn (mapcar composer tests))))
  )

(defmacro asserts-f (f &body tests)
  (asserts-f-body f tests))

関数と引数の組み合わせのテストを書くDSLです。

(asserts-f +
  (1 2)   => 3
  (3 4 5) => 12)

(defun add1 (x) (1+ x))

(asserts-f add1
  1 => 2
  3 => 4
  6 => 8) ;; ここでエラー
広告を非表示にする