#:g1: Common Lispのデバッガを活用しよう (1)

Posted 2022-04-04 17:00:34 GMT

Common Lispでは通常、エラーが発生するとデバッガが起動してきて後続の処理をどうするかを訊ねられますが、個人的にはあまり活用できていないので、デバッガ活用方法を模索して行きたいところです。
今回は、初心にかえってデバッガの使い方を点検してみましょう。

デバッガを終了させる

Common Lispを始めてまず面喰うのが、「デバッガが起動してくること」そのものですが、処理系ごとにデバッガから抜けるコマンドが違っていたりして結構厄介です。
とはいえ、SLIME経由で使えばその辺りは統一したインターフェイスになり、q(uit)か、a(bort)を押下すればデバッガから抜けられます。
処理系が用意しているデバッガの場合は、トップレベルに抜けるコマンドが大抵:ret(urn)や、:topで、:a(bort)で該当のエラーから抜けます。

デバッガとコードを行ったり来たりする

デバッガを終了させることができれば、デバッガは起動してもすぐ閉じる、という運用が可能になります。
実質エラーの通知画面のような存在にすることも可能なのですが、それでは寂しいので、もう一歩踏み込んでみましょう。

下記のような欠陥があるコードを用意し、(a)を評価します。

(defun a ()
  (- (b)))

(defun b () (* (parse-integer (c)) 2))

(defun c () (elt '("x" "1") (random 3)))

エラー発生でデバッガ起動、

The index 2 is out of range for sequence ("x" "1").
   [Condition of type CONDITIONS:INDEX-OUT-OF-RANGE]

Restarts: 0: [RETRY] Retry SLIME interactive evaluation request. 1: [*ABORT] Return to SLIME's top level. 2: [ABORT] Quit process.

Backtrace: 0: (CONDITIONS::CONDITIONS-ERROR :INVISIBLEP T CONDITIONS:INDEX-OUT-OF-RANGE (:INDEX 2 :SEQUENCE ("x" "1") :DATUM 2 ...)) 1: (ERROR CONDITIONS:INDEX-OUT-OF-RANGE :INDEX 2 :SEQUENCE ("x" "1") ...) 2: (ELT ("x" "1") 2) 3: (B) 4: (A) 5: (SYSTEM::%INVOKE :INVISIBLEP T) 6: (SYSTEM::%EVAL (A)) 7: (EVAL (A)) 8: ((SUBFUNCTION 1 (SUBFUNCTION 1 SWANK:INTERACTIVE-EVAL))) --more--

とりあえずは、インデックスの範囲超過のエラーですが、エラー発生地点からトップレベルの方に向って一番近い自作の関数を確認します。
SLIMEのデバッガの表示では上から下に向って探してエラーから一番近い自作関数というと、(B)です。

(B)の上で、M-.(slime-edit-definition)するとソースコードにジャンプするので修正を検討します。

(defun b ()
  (* (parse-integer (c)) 2))

このコードには範囲超過のコードは含まれていないのでスキップだろうということで、自作関数の(c)を確認するため、M-.でソースに飛びます。

(defun c ()
  (elt '("0" "1") (random 3)))

(elt '("0" "1") (random 3))

がおかしいので、

(elt '("0" "1") (random 2))

に変更して、(c)を再コンパイルします(slime-compile-defun)

修正が済んだので、M-,(slime-pop-find-definition-stack)で戻ります。

(b)の定義に戻るので、更にM-,してデバッガ画面の、(b)の呼び出しの箇所(フレーム)に戻ります。 修正は終ったので、ここでrでフレームをリスタートします。

これで終了と思いきや、

No integer present in "x" from 0 to 1, parse-integer expected one.
   [Condition of type CONDITIONS::SIMPLE-PARSE-ERROR]

Restarts: 0: [RETRY] Retry SLIME interactive evaluation request. 1: [*ABORT] Return to SLIME's top level. 2: [ABORT] Quit process.

Backtrace: 0: (CONDITIONS::CONDITIONS-ERROR :INVISIBLEP T CONDITIONS::SIMPLE-PARSE-ERROR (:FORMAT-STRING "No integer present in ~S from ~D to ~D, parse-integer expected one." :FORMAT-ARGUMENTS ("x" 0 1))) 1: (ERROR CONDITIONS::SIMPLE-PARSE-ERROR :FORMAT-STRING "No integer present in ~S from ~D to ~D, parse-integer expected one." :FORMAT-ARGUMENTS ("x" 0 1)) 2: (B) 3: (A) 4: (SYSTEM::%INVOKE :INVISIBLEP T) 5: (SYSTEM::%EVAL (A)) 6: (EVAL (A)) 7: ((SUBFUNCTION 1 (SUBFUNCTION 1 SWANK:INTERACTIVE-EVAL))) --more--

となりました。 そういえば、パーズ対象は36進数の表現でした(そういうことにしましょう)ので修正します。

先程と同じく、(b)のフレームの上で、M-xし、定義に飛びます。
定義を、

(defun b ()
  (* (parse-integer (c) :radix 36.) 2))

のように修正し、M-,でデバッガに戻ります。 (b)のフレームの上で、rでリスタートすると、返戻値-66で正常に関数が終了しました。

まとめ

大体こんな感じのことを繰り返すことが多いのですが、今回の例でいうと(c)のフレームが見えていてくれれば、もっと簡単です。 この辺りは、SLIME経由でなく、処理系備え付けのデバッガであれば、表示してくれることもあります。

また、デバッグ設定の具合によって変化するので、デバッガを活用してみるということであれば、

(declaim (optimize (debug 3) (safety 3)))

あたりの設定で開発するのが吉かと思います。

また、処理系によっては手前のフレームからステップ実行したりすることもできます。 個人的にはステップ実行が便利に感じたことがあまりないので、エラーメッセージを良く読んで、ソースに飛ぶ位で十分かなという感じです。

今回の記事より深い内容はあまり知らないのですが、今後も便利な方法を模索してみたいと思います。

関連


HTML generated by 3bmd in LispWorks 8.0.0

comments powered by Disqus