9

LYAHの最後の章を読み、ListZipperに会ったとき、ソースコードがより明確になるように、それをStateモナドにするという割り当てを自分に与えました。

manipList = do
    goForward
    goForward
    goBack

同時に、Writerモナドを利用してこのプロセスのログを保持したかったのですが、これら2つのモナドを組み合わせる方法がわかりませんでした。

私の解決策は[String]を状態内に保持することでした、そして私のソースコードは

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)

しかし、問題は、ログを手動で管理する必要があるため、これが適切な解決策ではないと思うことです。StateモナドとWriterモナドを組み合わせて連携できるようにする別の方法はありますか?

4

2 に答える 2

17

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

注:トランスフォーマーのタイプは大文字で終わるという規則がありTます。つまりMaybe、とWriterは通常のモナドであり、MaybeTWriterTはそれらのトランスフォーマーに相当します。

核となるアイデアは非常に単純です。モナドの束の場合、バインド時の動作を組み合わせることが簡単に想像できます。最も簡単な例はMaybeです。バインド時にMaybe伝播するだけであることを思い出してください。Nothing

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

したがって、この動作でモナドを拡張することは容易に想像できます。最初にチェックしてNothingから、古いモナドのバインドを使用するだけです。タイプはまさにこれを行います。MaybeT既存のモナドをラップし、各バインドの前に次のようなチェックを付けます。returnまた、基本的に値をaでラップしてからJust、内部モナドのを使用して実装する必要がありますreturn。すべてを機能させるための配管ももう少しありますが、これは重要なアイデアです。

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

モナド変換子は、必要に応じてモナドを「組み合わせる」ための非常に一般的な方法です。トランスフォーマーとして最も一般的に使用されるモナドのバージョンがありますが、注目すべき例外はIO常にモナドスタックのベースにある必要があります。あなたの場合、との両方WriterTStateT存在し、プログラムに使用できます。

于 2012-09-08T08:46:46.043 に答える
13

Tikhon Jelvisは、モナド変換子で良い答えを出します。ただし、迅速な解決策もあります。

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

于 2012-09-08T09:29:48.133 に答える