web-dev-qa-db-ja.com

printfsでHaskellを「デバッグ」する方法は?

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

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

trace関数も見つかります: http://www.haskell.org/haskellwiki/Debugging#Printf_and_friends これはまさに私が望んでいるようですが、私はタイプがわからない:どこにも[〜#〜] io [〜#〜]はありません!誰かがトレース関数の動作を説明できますか?

61
Vinz

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

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

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

54
Daniel Velkov

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

ただし、自己責任で、それでもいくつかのIO a -> aを一緒にハックすることができます。つまり、不純なIOを実行します。これはハックであり、もちろん遅延評価に「苦しんでいます」が、それはデバッグのためにトレースが単純に行うことです。

それでも、デバッグには他の方法を使用する必要があります。

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

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

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

17
Dario

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

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

厳密な命令型言語では、これらは通常一致します。 Haskellでは、彼らはしばしばしません:

  • 中間値を記録すると、他の方法では破棄される用語の評価を強制するなど、実行時の動作が変わる可能性があります。
  • 計算の実際のプロセスは、怠惰と共有された部分式のために、式の見かけの構造とは劇的に異なる可能性があります。

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

また、通常はすべてをモナドに入れる必要はなく、「ログ」値に書き込む必要のある関数だけを入れる必要があります。たとえば、次のことができます。ロギングを実行する必要がある可能性のある部分式だけを除外し、メインロジックを純粋のままにしてから、純粋関数とロギング計算を通常の方法でfmapsなどと組み合わせて、計算全体を再構築します。 Writerは、モナドの一種の申し訳ない言い訳であることに注意してください。ログを読み取る方法がなく、に書き込むだけです。つまり、各計算はそのコンテキストから論理的に独立しているため、物事を簡単にやりくりすることができます。

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

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

14
C. A. McCann

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

2
yatima2975

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

0
Gaius