2012-09-08 2 views
8

私はLYAHの最後の章を経て、ListZipperと会って、私は自分自身にそれのソースコードは、より多くのように明確に見えるように、Stateモナドにする割り当てを与えましたWriterモナドを利用してこのプロセスのログを残したかったのですが、この2つのMonadをどのように組み合わせるのか分かりませんでした。どのように私は国家と作家の両方を利用することができますhaskell?</p> <pre><code>manipList = do goForward goForward goBack </code></pre> <p>と同じで:

私のソリューションは、州内の[文字列]を維持するためだった、と私のソースコードは、

import Control.Monad 
import Control.Monad.State 

type ListZipper a = ([a], [a]) 

-- move focus forward, put previous root into breadcrumbs 
goForward :: ListZipper a -> ListZipper a 
goForward (x:xs, bs) = (xs, x:bs) 

-- move focus back, restore previous root from breadcrumbs 
goBack :: ListZipper a -> ListZipper a 
goBack (xs, b:bs) = (b:xs, bs) 

-- wrap goForward so it becomes a State 
goForwardM :: State (ListZipper a) [a] 
goForwardM = state stateTrans where 
    stateTrans z = (fst newZ, newZ) where 
     newZ = goForward z 

-- wrap goBack so it becomes a State 
goBackM :: State (ListZipper a) [a] 
goBackM = state stateTrans where 
    stateTrans z = (fst newZ, newZ) where 
     newZ = goBack z 

-- here I have tried to combine State with something like a Writer 
-- so that I kept an extra [String] and add logs to it manually 

-- nothing but write out current focus 
printLog :: Show a => State (ListZipper a, [String]) [a] 
printLog = state $ \(z, logs) -> (fst z, (z, ("print current focus: " ++ (show $ fst z)):logs)) 

-- wrap goForward and record this move 
goForwardLog :: Show a => State (ListZipper a, [String]) [a] 
goForwardLog = state stateTrans where 
    stateTrans (z, logs) = (fst newZ, (newZ, newLog:logs)) where 
     newZ = goForward z 
     newLog = "go forward, current focus: " ++ (show $ fst newZ) 

-- wrap goBack and record this move 
goBackLog :: Show a => State (ListZipper a, [String]) [a] 
goBackLog = state stateTrans where 
    stateTrans (z, logs) = (fst newZ, (newZ, newLog:logs)) where 
     newZ = goBack z 
     newLog = "go back, current focus: " ++ (show $ fst newZ) 

-- return 
listZipper :: [a] -> ListZipper a 
listZipper xs = (xs, []) 

-- return 
stateZipper :: [a] -> (ListZipper a, [String]) 
stateZipper xs = (listZipper xs, []) 

_performTestCase1 = do 
    goForwardM 
    goForwardM 
    goBackM 

performTestCase1 = 
    putStrLn $ show $ runState _performTestCase1 (listZipper [1..4]) 

_performTestCase2 = do 
    printLog 
    goForwardLog 
    goForwardLog 
    goBackLog 
    printLog 

performTestCase2 = do 
    let (result2, (zipper2, log2)) = runState _performTestCase2 $ stateZipper [1..4] 
    putStrLn $ "Result: " ++ (show result2) 
    putStrLn $ "Zipper: " ++ (show zipper2) 
    putStrLn "Logs are: " 
    mapM_ putStrLn (reverse log2) 

あるしかし、問題は、私は私を維持する必要があるため、私はこれは良い解決策だとは思わないということです手動でログします。州のモナドとライターのモナドを混ぜ合わせて、一緒に働けるようにする方法はありますか?

答えて

16

あなたはmonad transformersを探しています。基本的な考え方は、WriterTのようなタイプを定義し、別のモナドを取り、それをWriterと組み合わせて新しいタイプ(WriterT log (State s)など)を作成するというものです。

注:変圧器のタイプは大文字のTで終わるという規則があります。従ってMaybeWriterは普通のモナドであり、MaybeTWriterTは変圧器の同等物です。

コアのアイデアは非常に簡単です。モナドの束の場合、バインドでの動作を簡単に組み合わせることができます。最も単純な例はMaybeです。

Nothing >>= f = Nothing 
Just x >>= f = f x 

だから、この動作と任意のモナドを拡張想像するのは簡単なはず:すべてのことMaybeはありませんが、バインドにNothingを伝播していることを思い出してください。最初にNothingをチェックし、古いモナドのバインドを使用するだけです。 MaybeTタイプは正確にこれを行います。既存のモナドをラップし、各バインドの前にこのようなチェックを付けます。基本的にはJustに値をラップし、次に内側モナドのreturnを使用してreturnを実装する必要があります。すべてを稼働させるためにもう少し配管がありますが、これは重要なアイデアです。

Writerの動作は非常によく似ています。最初に新しい出力を結合し、古いモナドのバインドを使用します。これは本質的にはWriterTの動作です。他にもいくつかの詳細がありますが、基本的な考え方はかなりシンプルで便利です。

モナドトランスは、あなたが望むようにモナドを「結合する」のに非常に一般的な方法です。最も一般的に使用されているモナドのバージョンは変圧器ですが、例外的に例外的にモナドスタックの底にあるIOは例外です。あなたのケースでは、WriterTStateTの両方が存在し、あなたのプログラムに使用することができます。

+0

これはまさに私が欲しいものです!私は試してみました。両方とも(WriterT&StateT)うまくいきました。 – Javran

12

チッホン・ゼルヴィスはモナド・トランスで素敵な答えを出してくれます。しかし、迅速な解決策もあります。 mtl

Control.Monad.RWSモジュールはReaderWriterStateモナドの組み合わせであるRWSモナドを、エクスポート。

関連する問題

 関連する問題