4

そのため、私は State Monad を構築していましたが、デバッグを困難にしているその怠惰な性質に関するいくつかの問題に遭遇しました。

私の State モナドは、値のリストを取得し、それらを 1 つずつ状態の一部にプッシュすることによって動作します。次に、状態の他の部分を生成するために、各値の後に状態にある値を分析します。

デバッグが難しい理由を説明するために、この簡単な例を思いつきました。

module Main where

import Control.Monad.State
import Debug.Trace

runSim :: [Int] -> State String String
runSim [] = return =<< get
runSim (cur:xs) = do
    lst <- get
    let val =  (show $ 2*cur)
    put $ trace ((show $ length lst) ++ " " ++ (show cur)) ((val) ++ "," ++ lst)
    runSim xs

main :: IO ()
main = print $ evalState (runSim [1..10]) ""

これの出力は次のとおりです。

0 1
2 2
4 3
6 4
8 5
11 6
14 7
17 8
20 9
23 10
"20,18,16,14,12,10,8,6,4,2,"

ただし、トレース行を次のように変更すると:

put $ trace ((show cur)) ((val) ++ "," ++ lst)

出力は逆になります。

10
9
8
7
6
5
4
3
2
1
"20,18,16,14,12,10,8,6,4,2,"

しかし、最終結果は同じです。より自然にシーケンシャルになるように、デバッグ時に状態モナドの遅延を処理するより良い方法はありますか?

4

2 に答える 2

8

問題は、trace呼び出しが最後にのみ評価されることです。

計算は次のように状態を構築します(簡潔にするために2つの要素のみのリストを取ります)

runSim [1, 2] "" ~> ( (), state1@(trace (output 1 "") (logString 1 "")))
~> runSim [2] ( (), trace (output 2 state1) (logString2 state1))

したがって、最終状態ではtrace、最後にプッシュされたリスト要素の が最も外側になります。

2番目のケースでは、

output i _ = show i

トレース出力は以前に何が起こったかに依存しないため、trace最後にプッシュされたものが最初に実行されます。

しかし、最初のケースでは、

output i state = show (length state) ++ " " ++ show i

トレース出力は状態に依存するため、トレース出力を出力する前に状態を評価する必要があります。ただしstate、以前にプッシュされた への呼び出しtraceであるため、トレースを最初に実行する必要があるなどです。したがって、トレース出力のデータ依存関係により、プッシュの順序でトレースが確実に実行されます。

トレースがデータに依存せずにその順序で実行されるようにするには、trace呼び出しを から引き出すか、状態putの評価を強制する必要があります。put

put $! trace ((show $ length lst) ++ " " ++ (show cur)) ((val) ++ "," ++ lst)

また

trace ((show $ length lst) ++ " " ++ (show cur)) $ put ((val) ++ "," ++ lst)
于 2012-10-29T19:26:57.307 に答える
0
  1. というモナド法則ですm >>= return === m。したがって、 にreturn =<<アウェイを残すことができますrunSim []
  2. 現在の値を状態の先頭に追加するため、最終結果は間違った方法になりますが、必要なのは追加です。に変更((val) ++ "," ++ lst)する(lst ++ "," ++ val)と、出力は期待どおりになります: ",2,4,6,8,10,12,14,16,18,20"。(先頭のコンマについては後で気にすることができます。)
  3. 状態モナドは、状態を読み書きするためのものです。あなたの場合、あなたがしたいのは書くことだけであり、それがWriterモナドの目的です:

    import Control.Monad.Writer
    import Data.List (intercalate)
    
    main = putStrLn . intercalate ", " . map show $ execWriter (runSim [1..10])
    
    runSim :: [Int] -> Writer [Int] ()
    runSim [] = return ()
    runSim (x:xs) = tell [2*x] >> runSim xs
    
    ==> "2, 4, 6, 8, 10, 12, 14, 16, 18, 20"
    

    Writer [](書いたリストが長くなると、非常に効果がないことに注意してくださいDList。たくさん書いている場合に使用してください。)

  4. 一般的な注意: Haskell はほとんどの場合、適切に組み合わされた小さな関数で構成されているため、多くの場合、1 つの関数が何をするかについて簡単に推論できます。runSim現在の状態を取得し、リスト ヘッドの double を計算します。その後、いくつかのトレースval ++ "," ++ lst処理を実行し、最終ステップとして再帰する前に、新しい状態に戻します。Haskell の優れた点は、この関数でできることは、状態を取得し、乗算し、状態を入力することだけだということです。これの 1 つの反復を理解すると、すべての反復を理解できます。私が言いたいのは、あなたの場合でも怠惰の問題ではなかったということです.怠惰はあなたのプログラムの結果に影響を与えません. )。何でも構いませんtraceGHCがリストを最初に偶数で後方に歩き、次に奇数で前方に歩くことにした場合、結果は同じになります。traceただし、邪悪な魔法が含まれているため、混乱する可能性があります。私はtrace、あなたのプログラムの内部を調べるジャックハンマーの方法として more を使用します。
于 2012-10-29T17:51:47.003 に答える