ocamlコミュニティから来て、私はHaskellを少し学ぼうとしています。移行は非常にうまくいきますが、デバッグとは少し混乱しています。私は以前、ocamlコードに(たくさんの)「printf」を入れて、いくつかの中間値を調べたり、計算が正確に失敗した場所を確認するためのフラグとして使用していました。
Printfは[〜#〜] io [〜#〜]アクションなので、[〜#内のすべてのhaskellコードを持ち上げる必要がありますか? 〜] io [〜#〜]この種のデバッグを可能にするモナド?または、これを行うためのより良い方法はありますか(回避できるのであれば、私は本当に手でそれをしたくありません)
trace関数も見つかります: http://www.haskell.org/haskellwiki/Debugging#Printf_and_friends これはまさに私が望んでいるようですが、私はタイプがわからない:どこにも[〜#〜] io [〜#〜]はありません!誰かがトレース関数の動作を説明できますか?
trace
は、デバッグに最も使いやすい方法です。あなたが指摘した理由から、それはIO
にはありません。IO
モナドでコードを持ち上げる必要はありません。このように実装されています
trace :: String -> a -> a
trace string expr = unsafePerformIO $ do
putTraceMsg string
return expr
したがって、舞台裏にはIOがありますが、unsafePerformIO
はそれを回避するために使用されます。これは、参照透過性を壊す可能性のある関数であり、そのタイプを見ると推測できますIO a -> a
そしてその名前。
trace
は単に不純になります。 IO
モナドのポイントは、純度を維持し(型システムによって認識されないIO)、ステートメントの実行順序を定義することです。そうしないと、遅延によって実質的に定義されません。評価。
ただし、自己責任で、それでもいくつかのIO a -> a
を一緒にハックすることができます。つまり、不純なIOを実行します。これはハックであり、もちろん遅延評価に「苦しんでいます」が、それはデバッグのためにトレースが単純に行うことです。
それでも、デバッグには他の方法を使用する必要があります。
中間値をデバッグする必要性を減らす
ブレークポイントなどを使用します(コンパイラベースのデバッグ)
一般的なモナドを使用します。それでもコードがモナドである場合は、具体的なモナドとは別に記述してください。プレーンなtype M a = ...
の代わりにIO ...
を使用します。その後、トランスフォーマーを介してモナドを簡単に組み合わせ、その上にデバッグモナドを配置できます。モナドの必要性がなくなったとしても、純粋な値にはIdentity a
を挿入するだけで済みます。
ここで問題になっている「デバッグ」には、実際には2種類あります。
厳密な命令型言語では、これらは通常一致します。 Haskellでは、彼らはしばしばしません:
中間値のログを保持したいだけの場合は、さまざまな方法があります。たとえば、すべてをIO
に持ち上げるよりも、単純なWriter
モナドで十分です。関数が実際の結果の2タプルとアキュムレータ値(通常はある種のリスト)を返すようにするのと同じです。
また、通常はすべてをモナドに入れる必要はなく、「ログ」値に書き込む必要のある関数だけを入れる必要があります。たとえば、次のことができます。ロギングを実行する必要がある可能性のある部分式だけを除外し、メインロジックを純粋のままにしてから、純粋関数とロギング計算を通常の方法でfmap
sなどと組み合わせて、計算全体を再構築します。 Writer
は、モナドの一種の申し訳ない言い訳であることに注意してください。ログを読み取る方法がなく、に書き込むだけです。つまり、各計算はそのコンテキストから論理的に独立しているため、物事を簡単にやりくりすることができます。
しかし、場合によってはそれでもやり過ぎです。多くの純粋関数では、部分式をトップレベルに移動してREPLで試してみるだけでかなりうまくいきます。
ただし、純粋なコードの実行時の動作を実際に検査したい場合(たとえば、部分式が分岐する理由を理解するため)、一般に他の純粋なコードからそれを行う方法はありません。 code-実際、これは本質的に定義の純度です。したがって、その場合は、純粋な言語の「外部」に存在するツールを使用する以外に選択肢はありません。unsafePerformPrintfDebugging
-- errrなどの不純な関数、つまりtrace
--または変更された関数のいずれかです。 GHCiデバッガーなどのランタイム環境。
trace
はまた、印刷に対する議論を過大評価する傾向があり、その過程で怠惰の多くの利点を失います。
プログラムが終了するまで待ってから出力を調べることができる場合は、 Writer monad をスタックすることがロガーを実装するための古典的なアプローチです。私はこれを使用します ここ 不純なHDBCコードから結果セットを返します。