62

Ocamlコミュニティから来て、私はHaskellを少し学ぼうとしています。移行は非常にうまくいきますが、デバッグとは少し混乱しています。私は以前、ocamlコードに(たくさんの)「printf」を入れて、いくつかの中間値を調べたり、計算が正確に失敗した場所を確認するためのフラグとして使用していました。

printfはIOアクションなので、この種のデバッグを可能にするには、 IOモナド内のすべてのhaskellコードを持ち上げる必要がありますか?それとも、これを行うためのより良い方法がありますか(回避できるのであれば、私は本当に手でそれをしたくありません)

トレース関数 もあります:http: //www.haskell.org/haskellwiki/Debugging#Printf_and_friends これはまさに私が欲しいもののようですが、そのタイプがわかりません。どこにもIOがありません。誰かがトレース機能の動作を説明してもらえますか?

4

6 に答える 6

56

traceデバッグに使用する最も簡単な方法です。それはIOあなたが指摘した理由だけではありません:IOモナドでコードを持ち上げる必要はありません。このように実装されています

trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
    putTraceMsg string
    return expr

したがって、舞台裏にはIOがありますがunsafePerformIO、それを回避するために使用されます。IO a -> aこれは、参照透過性を損なう可能性のある関数であり、そのタイプと名前から推測できます。

于 2010-08-23T10:35:32.873 に答える
17

trace単純に不純になります。モナドのポイントは、IO純粋性を維持し (型システムによって認識されない IO がない)、ステートメントの実行順序を定義することです。これがなければ、遅延評価によって実質的に未定義になります。

ただし、自己責任で、いくつかをハックすることもできますIO a -> a。つまり、不純な IO を実行します。これはハックであり、もちろん遅延評価の影響を受けますが、それは trace が単にデバッグのために行うことです。

それにもかかわらず、おそらくデバッグには別の方法を使用する必要があります。

  1. 中間値のデバッグの必要性を減らす

    • 正確さが明らかな、小さくて再利用可能で明確な一般的な関数を記述します。
    • 正しいピースを組み合わせて、より正しいピースにします。
    • テストを作成したり、インタラクティブにテストしたりします。
  2. ブレークポイントなどを利用する(コンパイラベースのデバッグ)

  3. 汎用モナドを使用します。それにもかかわらず、コードがモナドである場合は、具体的なモナドから独立して記述してください。type M a = ...プレーンの代わりに使用しIO ...ます。その後、トランスフォーマーを介してモナドを簡単に結合し、その上にデバッグ モナドを置くことができます。Identity aモナドの必要性がなくなったとしても、純粋な値を挿入することができます。

于 2010-08-23T10:29:04.037 に答える
14

ここでは、実際には 2 種類の「デバッグ」が問題になっています。

  • 再帰関数への呼び出しごとに特定の部分式が持つ値など、中間値のログ記録
  • 式の評価の実行時の動作の検査

厳密な命令型言語では、これらは通常一致します。Haskell では、多くの場合、次のことを行いません。

  • 中間値を記録すると、そうでなければ破棄される用語の評価を強制するなど、実行時の動作を変更できます。
  • 計算の実際のプロセスは、遅延や部分式の共有により、式の見かけの構造とは劇的に異なる場合があります。

中間値のログを保持したいだけの場合、そうする方法はたくさんあります。たとえば、すべてをIOにリフトするのではなく、単純なWriterモナドで十分です。これは、関数が実際の値の 2 タプルを返すようにするのと同じです。結果とアキュムレータ値 (通常、ある種のリスト)。

また、通常はすべてをモナドに入れる必要はなく、「ログ」値に書き込む必要のある関数だけを入れる必要はありません。次に、通常の方法で純粋な関数とログ計算をfmaps などと組み合わせて、全体の計算を再構築します。Writerこれはモナドの言い訳のようなものだということを心に留めておいてください: ログから読み取る方法がなく、ログに書き込むだけで、各計算はそのコンテキストから論理的に独立しているため、処理が簡単になります。

しかし、場合によっては、それでさえやり過ぎです。多くの純粋な関数では、部分式をトップレベルに移動して REPL で試してみるだけで十分に機能します。

ただし、純粋なコードの実行時の動作を実際に検査したい場合、たとえば、部分式が発散する理由を理解するために、他の純粋なコードからそれを行う方法は一般にありません。実際、これは本質的に純粋さの定義。したがって、その場合、純粋な言語の「外側」に存在するツールを使用するしかありません。つまりunsafePerformPrintfDebugging、--errrなどの純粋でない関数かtrace、GHCi デバッガーなどの変更されたランタイム環境です。

于 2010-08-23T14:49:38.340 に答える
2

traceまた、印刷に関する議論を過大評価する傾向があり、その過程で怠惰の多くの利点が失われます。

于 2010-08-23T15:46:28.140 に答える
0

出力を調査する前にプログラムが終了するまで待つことができる場合、Writer モナドをスタックすることは、ロガーを実装するための古典的なアプローチです。ここではこれを使用して、不純な HDBC コードから結果セットを返します。

于 2010-10-23T12:35:03.263 に答える
-4

ええと、Haskell全体が遅延評価の原則に基づいて構築されているので(計算の順序は実際には非決定論的です)、printfの使用はほとんど意味がありません。

REPL + inspectの結果の値が実際にデバッグに十分でない場合は、すべてをIOにラップすることが唯一の選択肢です(ただし、Haskellプログラミングの正しい方法ではありません)。

于 2010-08-23T10:19:58.117 に答える