CL入門 No.5 - Debugger, Inspector

Debuggerとは?

Common LispではANSI Common Lispの仕様としてデバッガ機能*1の存在があります。
具体的な操作方法や機能に関しては処理系依存となりますが、
一般的によく利用されるデバッガ機能は標準規格化されています。

デバッガへの突入

プログラム中でコンディション*2が発生すると、
通常はハンドラ*3が捕捉して解決しますが、ハンドラが捕捉しなかった場合、
デバッガが受け取り対話モードが開始されます。

下記のコードをREPL上で試してみましょう。

(error "welcome to underground...")

error関数は、エラーコンディションを発生させる関数です。
コンディションハンドラも設定されていないので、デバッガがコンディションを受け取ります。

SBCLのデバッガ

Common Lispの処理系にはデバッガ必ず付属しています。
SBCLのデバッガを上のコードで立ち上げると、次のような表示がされるでしょう。

debugger invoked on a SIMPLE-ERROR in thread
#<THREAD "main thread" RUNNING {1002A712E3}>:
  welcome to underground...

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-INT:SIMPLE-EVAL-IN-LEXENV
 (ERROR "welcome to underground...")
 #<NULL-LEXENV>)
0]

主要な操作は次のようなものです。

  • ? - 操作方法を表示
  • 数字 - 対応するリスタート*4
  • 式 - (REPL特有の機能などは制限されるものの)ほぼ任意を実行できる

実際のデバッグではステップ実行やバックトレースが役に立ちますが、
デバッグ機能の詳しい説明は後々の記事にまとめます。

今回はデバッガに入るとリスタートできるということだけ覚えてみましょう。

REPLを立ち上げて、おもむろに下のコードを打ってみましょう。

(defun hoge () (+ 10 *hige*))

コンパイルは通りますが*hige*は未定義の変数なので警告*5が出ます。
続けて定義したhoge関数を実行してみましょう。

(hoge)

すると次のようなに出力されデバッガが立ち上がります。

debugger invoked on a UNBOUND-VARIABLE in thread
#<THREAD "main thread" RUNNING {1002A712E3}>:
  The variable *HIGE* is unbound.

Type HELP for debugger help, or (SB-EXT:EXIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(HOGE)
0]

ここで「0」と入力すると番号0のリスタートが実行されます。
するとtop-level*6に戻るので、REPLの入力から再スタートすることができます。

Slimeのデバッガ(SLDB)

Slime環境で開発する場合、
SLDBという強力なデバッガを使用することができます。

先ほどと同様にREPLを立ち上げたら、次のように打ってみましょう。

(defun hoge () (+ 10 *hige*))
(hoge)

同様にやはり*hige*が未束縛なので、
コンディションが発生し、デバッガが立ち上がります。

The variable *HIGE* is unbound.
   [Condition of type UNBOUND-VARIABLE]

Restarts:
 0: [RETRY] Retry SLIME REPL evaluation request.
 1: [*ABORT] Return to SLIME's top level.
 2: [ABORT] Abort thread (#<THREAD "repl-thread" RUNNING {1003F38063}>)

Backtrace:
  0: (HOGE)
  1: (SB-INT:SIMPLE-EVAL-IN-LEXENV (HOGE) #<NULL-LEXENV>)
  2: (EVAL (HOGE))
 --more--

SBCLに比べてリスタートの種類が増えています。
Slime環境では、サーバクライアントモデルで応対するので、
REPLのコマンドはSLIME REPLからのプログラムとして処理されます。
よってREPLのリクエストからリスタートできるように作られています。

  • 0番リスタート - REPLの式を再実行する
  • 1番リスタート - SlimeのREPLに戻る

デバッガが立ち上がったら「:」を入力してみましょう。
するとSlime Evalが立ち上がり任意の式を実行できます。

(defvar *hige* 20)

上の式を実行してから0番のリスタートから再開しましょう。
今度は*hige*が定義されているので、関数が正しく評価されます。

このようにデバッグしながらプログラムを書き換えることで、
デバッグ作業を効率良く進めていきましょう。

Inspectとは?

Inspectはオブジェクトを閲覧しながら編集することができる機能です。
Inspectも操作方法や機能に関しては処理系依存となります。

言語処理系のInspectorを起動したい場合は、inspect関数を呼び出します。

SBCLのInspect

SBCLのREPLを立ち上げたら、次のように入力してみましょう。

(defvar *hoge* '(10 20 30))
(inspect *hoge*)

Inspectが立ち上がり、次のように表示されます。

The object is a proper list of length 3.
0. 0: 10
1. 1: 20
2. 2: 30
>

Inspectの操作方法は標準Common Lispでは規定はありませんが、
おおよその処理系でhelpまたは?と入力すると、
操作説明が出力されるように作られています。*7

SBCLの場合、主に使うInspectコマンドは下記のものです。

  • ? - 操作ヘルプの表示
  • q, e - Inspectの終了
  • 数字 - 番号のオブジェクトに進入
  • u - 一つ前のオブジェクトに戻る
  • 式 - 任意の式を評価する

Debugger同様、Inspect中でも任意の式を評価できます。
SBCLの場合、
SB-EXT:*INSPECTED*にInspectされているオブジェクトが束縛されるので、
setfなどを利用してオブジェクトを編集することができます。

例えば上記のInspect中に、

(setf (elt *inspected* 1) 100)

を評価すると、リストの2番目のオブジェクトが100に変更されます。
rを入力してInspectの再表示をして確認してみましょう。

その後qでInspectから抜けた後に、

*hoge*

を評価して、Inspectを終了しても
オブジェクトの変更が反映されていることを確認できます。

Slime-Inspector

Slimeには特有のInspectorがあります。
Common Lispの処理系に依存しないでInspectを利用したい場合に便利です。
M-x Slime-InspectでInspectorを起動できます。

Slimeの場合、処理系標準のInspectのように、
Inspectされているオブジェクトを直接何らかの変数にバインドしません。
代わりに、Inspectされているオブジェクトを「*」*8に束縛する機能があります。

Slime-Inspectorの主な操作は下記のようになります。

  • q - Inspectorの終了
  • l - 一つ前のオブジェクトに戻る
  • M-RET - *にInspectしているオブジェクト束縛する

おわりに

ここまでで、代表的なCommon Lispの開発環境の説明は終了です。
あくまでツールの基本的な使い方のみ解説しましたので、
応用編は随時記事にしていく予定です。

次の記事からはCommon Lispでのプログラミングを始めます。

*1:プログラムの不具合を突き止めるためのプログラマ補助機能の事

*2:プログラム中の例外状況の事。コンディションシステムはまた後日まとめます。

*3:発生した例外を受け取って例外処理を実行する機能

*4:プログラムをリスタート地点から再開する機能。Common Lispでは標準で規定されている。

*5:警告もまたコンディションの一つです。

*6:プログラム中で一番外側の環境のこと。

*7:HyperSpecを読む限り、?でキーヘルプが見れることはANSI Common Lispで推奨されている仕様です

*8:cl:*はtop-levelで直前の評価値が束縛されています。