3

私は命令型言語、主にC ++とCでプログラミングを学んだので、機能的なアプローチは私にとって非常に新しいものです。

以前に関数/メソッドを書いていたとき、私は通常「インクリメンタル」アプローチを取りました(おそらくほとんどの人がそうします):コードの小さな部分を書き、それからこれまでの結果が期待どおりかどうかをチェックします(通常は単にそれらを印刷することによって) stdoutwithprintfまたはstd::cout)、アルゴリズムを改良し、アルゴリズムを強化し、これまでの結果が期待どおりかどうかを確認し(通常、printfまたはstd :: coutを使用してstdoutに出力するだけで)、改良します…私は非常にメソッド全体を1つのピースにまとめることはめったにありません。

この「インクリメンタル」アプローチに不可欠なのは、診断出力(上記の例ではprintfまたはstd :: cout)を持つ機能です。しかし、Haskellでは(今のところ私が理解している限り)、「putStrLn」はIOのみを返すため、「putStrLn」を使用してstdoutに何かを記述したい場合は、関数のシグネチャを変更する必要があります。印刷したい情報が含まれているが、「putStrLn」を呼び出した瞬間に印刷されないモナドですね。したがって、診断出力に「putStrLn」を使用するたびに、現在の関数のシグネチャや、他のすべての関数が呼び出す方法などを変更する必要があります…</ p>

では、関数の「ローカル変数」の値を標準出力に出力するための安価で簡単な方法はありますか?

それとも、ハスケルのプログラミングの基本的な部分を理解していないという兆候を私が求めているという単なる事実ですか?

4

6 に答える 6

13

あなたがやりたいことをする良い方法はありません。あなたは近づくことができますがDebug.Trace、Haskellの非標準的な評価順序のため、学習中にそれをお勧めしません。Haskellは、CやC ++のような言語のように、「変数」の値を順番に設定することによっては機能しません。怠惰なため、Haskell式は用途に応じた順序で評価されるため、増分値は実際には機能しません。

Haskellは式指向言語です。あなたの利点にそれを使用してください:

  1. 短い関数を記述します。各関数がこのように何をするかを確認する方が簡単です。ほとんどの関数は方程式ごとに1行である必要があり、実際の「1つのライナー」が一般的である必要があります。
  2. REPLを使用します。あなたは常にGHCiであなたのコードを実験しているべきです
  3. 型システムを使用します。Haskellの型システムは、ほとんどの命令型言語の型システムよりも桁違いに便利です。タイプは、マシンでチェックされた方法で意図を文書化します。タイプを理解せずにコードを理解することは期待できません。コードを書くとき、型を正しく理解すれば、ほとんどの方法で完了します。

上記の提案を組み合わせてください。GHCiの式の型は。で取得できます:t

于 2013-02-10T10:07:50.170 に答える
9

これは奇妙なことです。あなたが慣れ親しんでいる言語でReadEvalPrint Loop(REPL)がないので、コードをテストするのにどれだけの作業が必要か、私は永遠にイライラします。REPLは、私のインクリメンタルコード開発の基本です。これを使用すると、大量の印刷ステートメントを追加しなくてもコードをテストできます。

  • エディターと同時にGHCiを開いてください。
  • より小さな、単一目的の関数を記述します。これは最初は奇妙に思えますが、関数適用はHaskellの基本的な作業単位であり、命令型言語で発生するようなオーバーヘッドはありません。
  • 関数を作成するたびに:r、GHCiで実行し、さまざまな入力でテストします。
  • Haskellは非常に密度が高いので、別の関数にする価値があると見なされるものは、画面上で以前よりもはるかに短くなります。

時折、あなたは長いモナディック計算か何かで立ち往生してしまうことがあります。GHCiを使用すると、ブレークポイントを設定できます。コードを編集せずに少し混乱して調査できるため、コードに印刷ステートメントを追加するよりもブレークポイントを使用できます。最も重要なのは、型署名にShow制約を追加する必要がないことです。

完了したら、無償で短いヘルパー関数を手動でインライン化し、でコンパイルできますghc -O2

(手動で追加された印刷ステートメント、またはDebug.Traceモジュールを使用することは、私の経験ではこれと比較して完全な苦痛です。)

概要:可能な限り、テスト中にコードを編集することは避けてください。GHCiを多用します。

于 2013-02-10T14:56:33.530 に答える
1

まず、関数をロードしghciてそこで操作することにより、関数をデバッグできます。

trace次に、 fromを使用しDebug.Traceて、式が評価されるときに文字列を出力できます。ただし、Haskellは遅延評価を使用するため、ほとんどの場合、式は予想とは異なる時間に評価されることに注意してください。ウィキブックスHaskellwikiも参照してください。(内部的traceには安全でない呼び出しを使用しているため、純粋なコード内でも出力を出力できます。通常はそれらを使用することは想定されていませんが、この特定の場合は問題ありません。)

于 2013-02-10T10:07:50.410 に答える
1

Debug.Traceモジュールtraceの関数を使用して、純粋関数に不純なデバッグ出力をすばやく追加することができます。これは、2番目の引数/戻り値が強制されたときに最初の引数を出力するという追加の副作用を伴う2番目の引数を返す関数です。

最終的なコミットやその他の永続的なコードに終わらない限り、これを一時的にデバッグに使用することはまったく問題ないと思います。また、メッセージが出力される順序は評価の順序と一致します。これはデバッグにも役立ちますが、出力の優先順序であるとは限りません。

これを頻繁に使用する必要がある場合は、コードを小さな関数に分解する必要があることを示している可能性もあります。これにより、引数を指定して戻り値を確認するだけで、コードの動作を簡単に調べることができます。

于 2013-02-10T10:03:42.297 に答える
0

概算:

  1. GHCiデバッグは、コードを乱雑にすることなくローカル変数を出力する方法です。

  2. 厳密または遅延のWriterTモナド変換子は、トレースされた値を結果と組み合わせて返す場合、ログをシリアル化できます。

{-# LANGUAGE PackageImports #-}
-- import qualified "transformers" Control.Monad.Trans.Writer.Strict as W
import qualified "transformers" Control.Monad.Trans.Writer.Lazy as W

compute:: Int -> Int -> (Int, Int)
compute x y = (result, local)
  where
    local = 2 * x
    result = local + y

test :: (Monad m) => W.WriterT String m Int
test = do
  let (r1, local1) = compute 5 3
  W.tell $ "local1= " ++ show local1 ++ "\n"

  let (r2, local2) = compute 2 2
  W.tell $ "local2= " ++ show local2 ++ "\n"

  return $ r1 + r2

main = do
  (r, logs) <- W.runWriterT test
  putStrLn logs
  putStrLn $ "result= " ++ show r

出力:

local1 = 10
local2 = 4

結果=..。
于 2013-02-11T11:13:20.033 に答える
0

あなたが説明しているものに似ているように見えるものを構築する方法のかなり短い例がここにあります。私が正しく読んでいれば、作者は、いわば計算の途中で印刷できる単純なモナドを作成します。

于 2013-02-10T12:03:43.017 に答える