printfsでHaskellを「デバッグ」する方法は?
-
30-09-2019 - |
質問
OCAMLコミュニティから来て、私はハスケルを少し学ぼうとしています。移行は非常にうまくいきますが、私はデバッグと少し混乱しています。以前は、(多くの)「Printf」をOCAMLコードに入れて、中間値を検査するか、計算が正確に失敗した場所を確認するためにフラグとして使用していました。
printfはです io アクション、すべてのHaskellコードを内部に持ち上げる必要がありますか io この種のデバッグにできるモナド?または、これを行うためのより良い方法がありますか(それが避けられれば、私は本当に手作業でそれをしたくありません)
私も見つけます 痕跡 働き :http://www.haskell.org/haskellwiki/debugging#printf_and_and_and_andそれはまさに私が欲しいものですが、私はそれがタイプだとは理解していません:ありません io どこでも!誰かが私にトレース関数の動作を説明できますか?
解決
trace
デバッグに使用するのが最も簡単です。入っていません IO
あなたが指摘した理由のために正確に:であなたのコードを持ち上げる必要はありません IO
モナド。このように実装されています
trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
putTraceMsg string
return expr
そのため、舞台裏にIOがありますが unsafePerformIO
それから逃げるために使用されます。それは、そのタイプを見ることができる参照透明性を潜在的に破壊する機能です IO a -> a
また、その名前。
他のヒント
trace
単に不純にされています。のポイント IO
Monadは、純度(タイプシステムに気付かれない)を保存し、ステートメントの実行の順序を定義することです。それ以外の場合は、怠zyな評価を通じて事実上未定義です。
しかし、自己責任で、それでもあなたは一緒にハッキングすることができます IO a -> a
, 、つまり、不純なioを実行します。これはハッキングであり、もちろん怠zyな評価から「苦しむ」ことがありますが、それはトレースが単にデバッグのために行うことです。
それにもかかわらず、あなたはおそらくデバッグのために他の方法を行くべきです:
中間値をデバッグする必要性を減らす
- 正確さが明らかな小さな、再利用可能な明確な一般的な機能を書いてください。
- 正しいピースを組み合わせて、より大きな正しいピースにします。
- 書く テスト または、インタラクティブにピースを試してみてください。
ブレークポイントなどを使用する(コンパイラベースのデバッグ)
一般的なモナドを使用します。それにもかかわらず、コードがモナディックである場合は、具体的なモナドから独立して書いてください。使用する
type M a = ...
平野の代わりにIO ...
. 。その後、トランスを通してモナドを簡単に組み合わせて、その上にデバッグモナドを置くことができます。モナドの必要性がなくなったとしても、挿入することができますIdentity a
純粋な値の場合。
それが価値があることについては、ここに問題には実際に2種類の「デバッグ」があります。
- 特定のサブエクスペッションが各呼び出しに再帰関数にある値など、中間値を記録する中間値の記録
- 式の評価の実行時間動作を検査する
厳格な命令的な言語では、これらは通常一致します。 Haskellでは、彼らはしばしばそうではありません:
- 中間値を記録すると、それ以外の場合は破棄される用語の評価を強制するなど、ランタイムの動作を変更できます。
- 実際の計算プロセスは、怠inessと共有されたサブ発現による式の見かけの構造と劇的に異なる場合があります。
中間値のログを保持したい場合は、すべてを持ち上げるのではなく、そうする方法がたくさんあります。 IO
, 、 シンプルな Writer
Monadは十分であり、これは、機能を実際の結果の2タプルとアキュムレータ値(通常、何らかのリスト)を返すようにすることに相当します。
また、通常は置く必要はありません すべての モナドには、「ログ」値に書き込む必要がある関数のみです。インスタンスのために、ロギングを行う必要があるかもしれないサブエクスペッションのみを考慮し、メインロジックを純粋にしてから、純粋に組み合わせて全体的な計算を再組み立てできます。通常の方法で関数とロギング計算 fmap
sなど。それを念頭に置いて Writer
モナドの申し訳ありませんが、読む方法がありません から ログのみに書き込み、各計算はそのコンテキストから論理的に独立しているため、物事を簡単にやり取りできます。
しかし、場合によってさえ、それはやり過ぎです - 多くの純粋な機能のために、サブエクスペッションをトップレベルに移動し、REPLで物事を試してみるだけです。
ただし、純粋なコードのランタイム動作を実際に検査したい場合 - 例の場合、サブエクセプションが分岐する理由を把握する - 一般的にはあります 他の純粋なコードからそうする方法はありません- 実際、これは本質的にです 意味 純度の。したがって、その場合、純粋な言語の「外部」に存在するツールを使用する以外に選択肢はありません。 unsafePerformPrintfDebugging
- エラー、つまり trace
- または、GHCIデバッガーなどの修正されたランタイム環境。
trace
また、印刷に関する議論を過度に評価し、その過程で怠lazの多くの利点を失う傾向があります。
さて、Haskell全体は怠zyな評価の原則を中心に構築されているため(計算の順序は実際には非決定的ではありません)、Printfの使用はほとんど意味がありません。
REPL+の検査結果の値だけではデバッグに十分ではない場合、すべてをIOに包むことが唯一の選択です(ただし、Haskellプログラミングの正しい方法ではありません)。