- SLIMEが固まった状態は「SLDBが開いている」「REPLが返らない」「プロセスごと無応答」の3種類に分類でき、それぞれ対処法が異なります。
- EmacsでCommon Lispを動かす環境はSBCL・Swank・SLIMEの3層で構成され、エラーが起きるとSLDBバッファがデバッガ情報を表示します。
- SLDBではABORTで中止、USE-VALUEで代替値を渡して続行など、エラーの種類に応じたrestartを選んで処理を再開できます。
- バックトレースのフレームをtで展開してローカル変数を確認し、vでソース位置に飛び、eでその場で式を評価するといったデバッグ操作が一通りEmacs上で完結します。
1. SBCL・SLIME・SLDB の役割分担
「何がどこにあるか」を図で確認しましょう。
SBCL が Lisp コードを実行します。
エラーが起きると SBCL 自身のデバッガが *DEBUGGER-HOOK* 経由で起動しようとしますが、そこに Swank が割り込んでデバッガの情報を Emacs に送ります1。Swank は SLIME がサーバ側に自動ロードするコンポーネントです。Emacs 側の SLIME がその情報を受け取って表示するのが SLDB バッファです。
SLDB は「SBCL のデバッガを Emacs 上で操作できるようにした SLIME のフロントエンド」と言えます2。
操作は次の2レイヤーで考えると整理しやすくなります。
| レイヤー | 担当 | 操作の例 |
|---|---|---|
| SBCL 側 | エラーの種類・restart の定義・バックトレースの生成 | エラーメッセージの内容、restart に何が出るか |
| Emacs/SLIME 側 | SLDB バッファの表示・キー操作・REPL との連携 | a q t v e などのキー |
どの restart が出るかはエラーの種類によって SBCL が決めます。SLIME はその選択肢を画面に出して、キー操作を SBCL に伝えます3。
1.1. SLIME の起動・終了と inferior-lisp
M-x slime を実行すると、Emacs が SBCL を inferior-lisp として子プロセスで起動し、Swank サーバに接続します4。*inferior-lisp* バッファがその生の出力先で、REPL バッファとは別物です。
SLIME が使うバッファの対応は次のとおりです。
| バッファ | 内容 |
|---|---|
*slime-repl sbcl* | 普段使う REPL |
*inferior-lisp* | SBCL プロセスの生の出力(デバッグ用) |
*sldb …* | デバッガバッファ |
*slime-events* | Swank の通信ログ(通常は不要) |
終了方法は目的によって使い分けます。
REPL から Lisp を終了する場合は次のどちらかを使います。
CL-USER> (quit)
CL-USER> (sb-ext:exit)
Code language: CSS (css)
SBCL プロセスが終了し、SLIME の接続も切れます。Emacs コマンドから操作したい場合は M-x slime-quit-lisp でも同じ結果になります5。
接続がおかしくなったときは M-x slime-restart-inferior-lisp を使います。SBCL プロセスを強制終了して新しく起動し直すので、作業中の定義はすべて消えます6。
SBCL プロセスを残したまま Emacs との接続だけを切るには M-x slime-disconnect を使います。M-x slime-connect で再接続できます7。
2. SLIME が反応しなくなる3つの状態
「SLIME が固まった」と感じる場面は、実際には3つに分類できます。
2.1. 状態A: エラーが出て SLDB が開いている
SBCL がデバッガ待ち状態です。SLDB バッファに restart 一覧が表示されています。
restart を選べば解決します。バッファを C-x k で閉じても SBCL 側の状態は変わらないため、REPL が戻りません8。
2.2. 状態B: 計算中で REPL が返ってこない(SLDB も出ない)
無限ループや重い計算が走り続けています。SBCL はまだ正常で、デバッガにも入っていません。
C-c C-b、つまり M-x slime-interrupt を押します。
Emacs から SBCL プロセスに割り込みシグナルを送る操作です9。SBCL が割り込みを受け取ってデバッガに入り、SLDB が開きます。あとは状態A と同じ手順で対処できます。
CL-USER> (loop (format t "~%running")) ; ← 返ってこない
C-c C-b を押す
interrupted
[Condition of type SB-SYS:INTERACTIVE-INTERRUPT]
Restarts:
0: [CONTINUE] Continue from interrupted state.
1: [ABORT] Return to SLIME's top level.
1 を選ぶか a を押して REPL に戻る
Code language: PHP (php)
CONTINUE はループを再開するので、ここでは選びません。
2.3. 状態C: Lisp プロセス自体がおかしい
接続が切れた、REPL バッファが全く応答しない、SLDB も出ない、かつ C-c C-b も効かない状態です。M-x slime-restart-inferior-lisp で SBCL プロセスごと再起動します10。
3. SLDB の基本構造と操作
3.1. バッファの見方
The value "hello" is not of type NUMBER.
[Condition of type TYPE-ERROR] ← エラーの種類
Restarts: ← 再開手段の選択肢
0: [ABORT] Return to SLIME's top level.
Backtrace: ← 呼び出し履歴
0: (SB-KERNEL:TWO-ARG-+ 1 "hello")
1: (ADD-ONE "hello")
2: (SB-INT:SIMPLE-EVAL-IN-LEXENV ...)
...
Code language: PHP (php)
restart は SBCL が用意します。エラーの種類によって選択肢が変わります。a を押すと SLIME が「ABORT restart を選択」という命令を SBCL に送ります11。
3.2. restart の主な種類
| restart 名 | 意味 |
|---|---|
ABORT | 現在の評価を中止してトップレベルへ戻る |
CONTINUE | 処理を続ける(ワーニング等で有効) |
USE-VALUE | 代わりの値を指定して続行する |
STORE-VALUE | 値を変数に束縛して続行する |
RETRY | 同じ操作を再試行する |
迷ったら ABORT、つまり a を選べば安全に一段階戻れます12。
3.3. 基本キー
restart を選ぶキーです。
| キー | 操作 |
|---|---|
a | ABORT restart を実行する |
q | SLIME の安全な状態へ戻るリスタートを呼ぶ13 |
c | CONTINUE restart を実行する |
0〜9 | 番号で restart を選ぶ |
RET | カーソル位置の restart を実行する |
3.4. restart の使い方:具体例
3.5. a で中止してトップレベルへ戻る
CL-USER> (+ 1 "hello")
Code language: JavaScript (javascript)
The value "hello" is not of type NUMBER.
[Condition of type TYPE-ERROR]
Restarts:
0: [ABORT] Return to SLIME's top level.
Code language: PHP (php)
a を押すと CL-USER> プロンプトが返ってきます14。
3.6. USE-VALUE で代わりの値を渡して続行する
CL-USER> (* undefined-var 10)
Code language: JavaScript (javascript)
The variable UNDEFINED-VAR is unbound.
[Condition of type UNBOUND-VARIABLE]
Restarts:
0: [CONTINUE] Retry using UNDEFINED-VAR.
1: [USE-VALUE] Use specified value.
2: [STORE-VALUE] Set specified value.
3: [ABORT] Return to SLIME's top level.
Code language: PHP (php)
1 を押してミニバッファに 5 を入力すると、(* 5 10) として続行して 50 が返ります。2 の STORE-VALUE を選ぶと、UNDEFINED-VAR に値がセットされて以降の評価でも使えます15。
3.7. CONTINUE でワーニングを無視する
CL-USER> (defun example ()
(let ((unused 42))
:ok))
Code language: JavaScript (javascript)
コンパイル時にワーニングで SLDB が開くことがあります。c を押すか CONTINUE を選ぶと処理が続きます。
型エラーや未束縛変数エラーでは CONTINUE が出ないか、出ても意図しない動作になります。迷ったら a を選んでください16。
3.8. RETRY でファイル操作を再試行する
CL-USER> (open "/tmp/no-such-file.txt")
Code language: JavaScript (javascript)
The file /tmp/no-such-file.txt does not exist.
[Condition of type FILE-ERROR]
Restarts:
0: [RETRY] Retry opening /tmp/no-such-file.txt.
1: [USE-VALUE] Use an alternate file name.
2: [ABORT] Return to SLIME's top level.
Code language: PHP (php)
ファイルを作成してから 0 を選ぶと再度 open が実行されます。1 を選んでミニバッファに別パスを入力すれば、そのファイルで続行します17。
3.9. 判断フローまとめ
SLIME が反応しない
├─ SLDB バッファが開いている(状態A)
│ ├─ 中止したい → a (ABORT)
│ ├─ 代替値で続行したい → USE-VALUE / STORE-VALUE を番号で選ぶ
│ └─ ワーニングを無視したい → c (CONTINUE)
│
├─ REPL が返ってこない、SLDB が出ない(状態B)
│ └─ C-c C-b (slime-interrupt) でデバッガに入る → a
│
└─ 何も効かない(状態C)
└─ M-x slime-restart-inferior-lisp
SLIME を終了したい
├─ 通常: (quit) または M-x slime-quit-lisp
└─ 接続だけ切る: M-x slime-disconnect
Code language: PHP (php)
4. デバッグ機能の使い方
フレームを調べるキーです。
| キー | 操作 |
|---|---|
t | ローカル変数と引数を展開・折りたたむ |
v | そのフレームのソースコード位置を開く |
e | フレームの環境で式を評価する |
d | フレームの環境で式を評価しプリティプリントする |
i | フレームの環境で式を評価しインスペクタで開く |
r | 同じ引数でフレームを再実行する(実装依存)18 |
R | ミニバッファの値をフレームの戻り値として返す(実装依存) |
n p | フレーム間を移動する |
M-n M-p | フレーム間を移動しつつソースを開く |
4.1. バックトレースを読む
(defun divide (a b)
(/ a b))
(defun calculate (x)
(divide x 0))
CL-USER> (calculate 10)
arithmetic error DIVISION-BY-ZERO signalled
[Condition of type DIVISION-BY-ZERO]
Restarts:
0: [ABORT] Return to SLIME's top level.
Backtrace:
0: (SB-KERNEL:/-MAYBE-TRUNCATE 10 0)
1: (DIVIDE 10 0)
2: (CALCULATE 10)
3: (SB-INT:SIMPLE-EVAL-IN-LEXENV ...)
...
Code language: PHP (php)
フレーム番号 0 が最も内側、つまりエラー直前です。フレーム 1 が DIVIDE、フレーム 2 が CALCULATE なので、CALCULATE が 0 を渡したことが原因とわかります19。
4.2. t でローカル変数を展開する
フレームの行で t を押すと引数とローカル変数が表示されます20。
1: (DIVIDE 10 0)
Locals:
A = 10
B = 0
4.3. v でソース位置を開く
フレームにカーソルを置いて v を押すと、そのフレームのソース行にジャンプします。どのファイルのどの行かをすぐ確認できます21。
4.4. e でフレームの環境で式を評価する
フレームにカーソルを置いて e を押し、ミニバッファに式を入力します。そのフレームのローカル変数が有効な状態で評価されます。フレーム 1: (DIVIDE 10 0) で e を押して (* a 2) と入力すると 20 が返ります22。
4.5. d でプリティプリントする
d は e と同じですが、結果を別バッファにプリティプリントして表示します。ネストの深いリストを読むときに使えます。
4.6. i でインスペクタに渡す
i を押してミニバッファで式を入力すると、評価結果をインスペクタで開きます。リストや構造体の中身を掘り下げて見たいときに使います。インスペクタのスロットで RET を押すとさらに深い構造に入れます。q で SLDB に戻ります23。
4.7. r でフレームを再実行する
C-c C-c でフレームの関数を修正・再コンパイルしてから r を押すと、同じ引数でそのフレームだけ再実行します。最初から呼び直さずにその場で修正を試せます。SBCL では使えますが、実装によっては利用できない場合があります24。
4.8. 通しのデバッグ例
(defun process-items (items)
(mapcar (lambda (x) (* x 2)) items))
CL-USER> (process-items '(1 2 "three" 4))
The value "three" is not of type NUMBER.
[Condition of type TYPE-ERROR]
Restarts:
0: [ABORT] Return to SLIME's top level.
Backtrace:
0: (SB-KERNEL:TWO-ARG-* "three" 2)
1: ((LAMBDA (X)) "three")
2: (MAPCAR #<FUNCTION (LAMBDA (X))> ("three" 4))
3: (PROCESS-ITEMS (1 2 "three" 4))
...
Code language: PHP (php)
- フレーム
1でtを押してX = "three"を確認します - フレーム
3でtを押してITEMS = (1 2 "three" 4)を確認します。リストに文字列が混入しています aで ABORT して REPL に戻り、コードを修正します25。
(defun process-items (items)
(mapcar (lambda (x)
(if (numberp x)
(* x 2)
(error "Not a number: ~A" x)))
items))
Code language: JavaScript (javascript)- Swank は接続確立時に
call-with-debugger-hookを通じてswank-debugger-hookを*DEBUGGER-HOOK*にセットします。これにより SBCL のネイティブデバッガの代わりに SLDB が起動するようになります。 – swank.lisp (slime/slime GitHub) - デフォルトでは SLIME が起動した Lisp プロセスのメインスレッドのデバッグを担いますが、
swank:*global-debugger*をtにすると全スレッドのエラーを SLDB で扱えるようになります。 – Other configurables (SLIME User Manual, version 2.24) - SLIME は SBCL 以外にも Clozure CL、CLISP、ECL、Allegro CL など多くの Common Lisp 実装に対応しています。ただし
r(フレーム再実行)など一部の機能は実装によって使えない場合があります。 – SLIME – WikEmacs - 起動する Lisp 実装は
slime-lisp-implementations変数で切り替えられます。例:(setq slime-lisp-implementations '((sbcl ("sbcl")) (ccl ("ccl"))))と設定すると、M-- M-x slimeで起動する実装を選べるようになります。 – SLIME User Manual, version 2.32 (quit)は SBCL 独自の関数です。複数の Lisp 実装に対応するコードを書く場合は(uiop:quit)を使うのがポータブルです。uiopは ASDF に同梱されているため追加インストールは不要です。 – Scripting – Common Lisp Cookbook(sb-ext:exit)は:codeキーワード引数で終了コードを指定できます。例えば(sb-ext:exit :code 1)はエラー終了を意味します。スクリプトや CI 環境で終了コードを制御したいときに使います。 – SBCL User Manual- Swank サーバをリモートマシンで起動して SSH トンネル経由で接続する使い方もできます。ローカルの Emacs から
M-x slime-connectでホストとポートを指定するだけで、リモート Lisp 環境をそのままデバッグできます。 – SLIME User Manual, version 2.32 - SLDB の中でさらに式を評価してエラーが起きると、デバッガがネストして開きます。バッファ先頭に
[2]のようにネスト深度が表示されます。qを押すか、restart 一覧の「Return to SLIME’s top level」を選ぶと一番外に戻れます。 – CLiki: SLIME Features C-c C-bは SLIME マニュアルに記載されているslime-interruptのデフォルトキーバインドです。REPL バッファ内で有効です。 – Restarts (SLIME User Manual, version 2.24)slime-restart-inferior-lispは inferior-lisp プロセスを強制終了して再起動するため、REPL で定義した関数や変数はすべて消えます。重要な定義はファイルに保存してから実行してください。 – SLIME User Manual, version 2.32- バックトレースの表示形式は
swank:*sldb-printer-bindings*で制御できます。例えば(push '(*print-pretty* . t) swank:*sldb-printer-bindings*)を~/.swank.lispに書くと、バックトレースがプリティプリントされて読みやすくなります。 – Other configurables (SLIME User Manual, version 2.24) - restart は
restart-caseマクロを使ってユーザが独自に定義できます。例えばファイル読み込み関数の中で「別のファイル名を試す」restart を定義しておくと、エラー時に SLDB の選択肢に現れます。 – Beyond Exception Handling: Conditions and Restarts – Practical Common Lisp qが呼ぶリスタートはswank:*sldb-quit-restart*変数で変更できます。マルチスレッドアプリでスレッドを終了させたいときは(setf swank:*sldb-quit-restart* 'sb-thread:terminate-thread)のように設定します。 – Other configurables (SLIME User Manual, version 2.24)- ABORT は Common Lisp 標準で定義された restart 名です。実装は常にトップレベルに ABORT restart を用意することが推奨されており、SBCL では評価リクエストの外側に必ず存在します。 – 29.4.7. Establishing Restarts (CLtL2)
USE-VALUEとSTORE-VALUEはどちらも Common Lisp 標準で定義されている restart 名です。USE-VALUEは値をその場だけ使い、STORE-VALUEは変数や場所に値を格納して続行します。 – Condition System – Common Lisp Docs- ワーニングを自動的に無視したい場合は
(muffle-warning)関数やhandler-bindとの組み合わせでコード側から制御できます。コンパイル時の特定ワーニングを静かにするにはsb-ext:muffle-conditionsデクレアも使えます。 – SBCL User Manual restart-caseを使って独自のRETRYrestart を定義することもできます。例えばデータベース接続関数の中に「接続をリトライする」restart を定義しておくと、接続エラー時に SLDB から直接リトライを選べます。 – Common Lisp: A Tutorial on Conditions and Restarts- SLIME マニュアルでは「This command is not available in all implementations」と明記されています。SBCL では使用可能です。 – Miscellaneous (SLIME User Manual, version 2.24)
- バックトレースの
SB-KERNELやSB-INTで始まるフレームは SBCL の内部実装です。通常はこれらより番号の大きいフレーム(ユーザコード側)から原因を探します。 – Debugging – Common Lisp Cookbook tを押すと CATCH タグも表示されます。catchフォームを使ったコードをデバッグするとき、どのcatchが有効かを確認できます。 – Examining frames (SLIME User Manual, version 2.24)vでジャンプできるのは、ソース情報が保存された状態でコンパイルされた定義に限ります。C-c C-c(slime-compile-defun)でコンパイルした関数であればデバッグ情報が含まれます。ファイルをロードするだけではジャンプ先が得られない場合があります。 – Debugging – Common Lisp Cookbookeは結果をミニバッファの echo area に表示します。dは同じ評価を行いますが、結果を別の一時バッファにプリティプリントして開きます。ネストの深い構造を確認するときはdのほうが読みやすくなります。 – Examining frames (SLIME User Manual, version 2.24)- SLIME のインスペクタは
M-x slime-inspectでデバッガの外からも起動できます。ミニバッファで任意の式を入力してその値を調べることができ、SLDB を経由しなくても使えます。 – SLIME User Manual version 2.24 - SLDB の中から
C-c C-cを押すと、バックトレースに表示されている関数の定義を再コンパイルできます。再コンパイル後にrでそのフレームを再実行することで、プログラム全体を最初から動かし直さずにバグ修正を確認できます。 – Debugging – Common Lisp Cookbook - Common Lisp の条件システム全体については Peter Seibel 著 Practical Common Lisp 第19章「Beyond Exception Handling: Conditions and Restarts」で詳しく解説されています。同書はオンラインで無料公開されています。 – Beyond Exception Handling: Conditions and Restarts – Practical Common Lisp