ここでは、実際には 2 種類の「デバッグ」が問題になっています。
- 再帰関数への呼び出しごとに特定の部分式が持つ値など、中間値のログ記録
- 式の評価の実行時の動作の検査
厳密な命令型言語では、これらは通常一致します。Haskell では、多くの場合、次のことを行いません。
- 中間値を記録すると、そうでなければ破棄される用語の評価を強制するなど、実行時の動作を変更できます。
- 計算の実際のプロセスは、遅延や部分式の共有により、式の見かけの構造とは劇的に異なる場合があります。
中間値のログを保持したいだけの場合、そうする方法はたくさんあります。たとえば、すべてをIO
にリフトするのではなく、単純なWriter
モナドで十分です。これは、関数が実際の値の 2 タプルを返すようにするのと同じです。結果とアキュムレータ値 (通常、ある種のリスト)。
また、通常はすべてをモナドに入れる必要はなく、「ログ」値に書き込む必要のある関数だけを入れる必要はありません。次に、通常の方法で純粋な関数とログ計算をfmap
s などと組み合わせて、全体の計算を再構築します。Writer
これはモナドの言い訳のようなものだということを心に留めておいてください: ログから読み取る方法がなく、ログに書き込むだけで、各計算はそのコンテキストから論理的に独立しているため、処理が簡単になります。
しかし、場合によっては、それでさえやり過ぎです。多くの純粋な関数では、部分式をトップレベルに移動して REPL で試してみるだけで十分に機能します。
ただし、純粋なコードの実行時の動作を実際に検査したい場合、たとえば、部分式が発散する理由を理解するために、他の純粋なコードからそれを行う方法は一般にありません。実際、これは本質的に純粋さの定義。したがって、その場合、純粋な言語の「外側」に存在するツールを使用するしかありません。つまりunsafePerformPrintfDebugging
、--errrなどの純粋でない関数かtrace
、GHCi デバッガーなどの変更されたランタイム環境です。