2

デバッグ機能に問題があります。以下にある特定のコマンドの実行を定義することになっています。次に、実行が呼び出されるたびに現在のメモリ値を再帰的に出力するデバッグ関数を作成する必要があります。

exec(execution)、debug、およびデータ型のコードは次のとおりです

data Com = Ass Char Exp 
         | While Exp Com 
         | Seq Com Com
           deriving Show

exec :: Memory -> Com -> Memory
exec m (Ass c e) = update m (c, (eval m e))
exec m (While e c) = if (eval m e) == 0 then m
                     else exec (exec m c) (While e c)
exec m (Seq c1 c2) = exec (exec m c1) c2


debug :: Memory -> Com -> [Memory]
debug m (Ass c e) = update m (c, (eval m e)) : [m]
debug m (Seq c1 c2) = (exec (exec m c1) c2) :[m]
debug m (While e c) = if (eval m e) == 0 then [m]
                      else (exec (exec m c) (While e c)) : [m]

com1 :: Com
com1 =  Seq (Ass 'z' (Num 1))       
        (While (Var 'y') (Seq (Ass 'z' (Mul (Var 'z') (Var 'y')))
                              (Ass 'y' (Add (Var 'y') (Num (-1))))))

関数を特定のメモリ状態とコマンドで実行すると、開始メモリと終了メモリのみが出力されます。たとえば、debug [('y',4)] com1すべてを実行した場合は[[('y',0),('z',24)],[('y',4)]]、印刷する必要があります。

  [('y',4)]
  [('y',4),('z',1)]
  [('y',4),('z',4)]
  [('y',3),('z',4)]
  [('y',3),('z',12)]
  [('y',2),('z',12)]
  [('y',2),('z',24)]
  [('y',1),('z',24)]
  [('y',1),('z',24)]
  [('y',0),('z',24)]

再帰的に出力するには、デバッグ関数で何を変更する必要があるのか​​を尋ねたいのですが。

4

3 に答える 3

6

と関数を比較するexecと、すべての中間状態が出力されないdebug理由がわかります。返される状態を取得して、元のメモリ状態にアタッチするだけです。debugexecm

execは中間メモリ状態を保存せず、debugは基本的にすでに保存されているため、。の代わりに再帰的に呼び出すようexecに書き直すことをお勧めします。また、の戻りタイプをに変更しましょう。最初のコンポーネントは以前のすべての状態のリストになり、2番目のコンポーネントは最新の結果になります。(厳密に言えば、最新の結果は履歴の一部であるため、これは必要ありませんが、コードをよりクリーンに保つのに役立ち、より一般的です-メモリ履歴以外のものを保持するように簡単に変更できます。実行された操作のログとして。)debugexecdebug([Memory], Memory)debug

とにかく、私たちはこのようなものを手に入れます:

debug :: Memory -> Com -> ([Memory], Memory)
debug m (Ass c e)   = ([m'],m')
  where m' = update m (c, eval m e)

残りはあなたにお任せします-それほど難しくはありません。

これはそれほど悪くはありません。ただし、まだいくつかの問題があります。

  • 完成したプログラムは確かにを使用しますが++、これは少し恐ろしいことです。プログラムがログの長さの2次式で簡単に終了する可能性があります(たとえば、ログが新しいものから順に書き込まれた場合、繰り返し追加します。初期の状態から長いリストの最後まで)。
  • 基本的に、元のを複製しました(そして少し醜くしました)exec

上記のプログラムは、状態のスレッド化として知られる関数型プログラミングの「パターン」の典型的な例です。ここでは、関数呼び出しのreturnタイプに情報を追加して、プログラムの履歴に関する情報を格納します。このプログラムは、実際には2つのタイプの状態をスレッド化するものと考えることができます。1つは検査と変更の両方を行うメモリ、もう1つは書き込みのみで読み取りは行わないログです。

もちろん、関数型プログラミングはすべて抽象化に関するものであり、上記の問題を解決し、「スレッド化」パターンを排除するためのよく知られた方法があります。モナドです。

Writerモナドを使用して、ログの明示的なスレッド化を排除できます。

ボーナス:状態モナドを使用して、メモリ状態の明示的なスレッド化を排除するために使用できます。

差分リストを使用して、潜在的に高価なの使用を排除できます++

これらすべてのトピックについて学ぶには、Learn You a Haskellの最後から2番目の章をお勧めします。その後、標準ライブラリで定義されているモナドを使用できるようになります。

于 2012-12-10T05:52:11.293 に答える
2

debug関数が呼び出しています(ログは記録されませexecん)。代わりに、debug再帰的に呼び出す必要があります。

これは、 「計算された値に加えてデータのストリームを生成する計算」に役立つWriterモナドについて学ぶ良い機会です。

于 2012-12-10T05:23:57.140 に答える
1

これはやり過ぎかもしれませんが、とにかく...

まず、これを州のモナドに入れて、あなたが何をしているのかがはっきり見えるようにします。

type MemState a = State Memory a
type EvalVal = ... -- result of eval


  -- You'd better define those two right /instead/ of 'eval' and 'update'
eval' :: Exp -> MemState EvalVal
eval' e = state $ \m -> (eval m e, m)
update' :: Char -> EvalVal -> MemState ()
update' c v = state $ \m -> ((), update m (c, v))

exec :: Com -> MemState ()
exec (Ass c e)   = eval' e >>= update' c
exec whileCom@(While e c) = do
        v <- eval'
        when (v /= 0) $ do
           exec c
           exec whileCom
exec (Seq c1 c2) = do
        exec c1
        exec c2

これで、user5402がすでに提案したように、デバッグはWriter:を追加することで実行できます。

type DebugState a = WriterT [Memory] MemState a

tellMemory :: DebugState ()
tellMemory = do
     m <- lift . state $ \m' -> (m',m')
     tell [m]

debug :: Com -> DebugState ()
debug com@(Ass _ _) = do
         tellMemory
         lift $ exec com
         tellMemory
debug (Seq c1 c2) = do
         tellMemory
         debug c1
         debug c2
debug com@(While e c) = do
         tellMemory
         v <- lift eval'
         when (v /= 0) $ do
            debug c
            debug com
于 2012-12-10T05:54:01.080 に答える