2012-09-08 15 views
8

kiedy przeszedł ostatnim rozdziale LYAH i spotkał się z ListZipper, dałem sobie zadanie, że aby to monada członkowskie tak, że kod źródłowy będzie wyglądać bardziej jasne jak:Jak mogę wykorzystać zarówno stan, jak i pisarz w haskell?

manipList = do 
    goForward 
    goForward 
    goBack 

a przy tym czas, chciałem zachować dziennik dla tego procesu, korzystając z monitu Writer, ale nie wiedziałem, jak połączyć te dwie Monady razem.

Moje rozwiązanie było zachować [łańcuch] wewnątrz państwa, a mój kod źródłowy jest

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) 

Ale problemem jest to, że nie sądzę, że jest to dobre rozwiązanie, ponieważ muszę utrzymać moją dzienniki ręcznie. Czy jest jakaś alternatywna metoda łączenia monady państwowej i monady pisarskiej, aby mogły współpracować?

Odpowiedz

16

Szukasz monad transformers. Podstawową ideą jest zdefiniowanie typu, takiego jak WriterT, który przyjmuje kolejną monadę i łączy ją z Writer, tworząc nowy typ (np. WriterT log (State s)).

Uwaga: istnieje konwencja, że ​​typy transformatorów kończą się kapitałem T. Tak więc Maybe i Writer są normalnymi monadami, a MaybeT i WriterT są ich odpowiednikami transformatorowymi.

Zasadniczy pomysł jest bardzo prosty: dla wielu monad można łatwo wyobrazić sobie połączenie ich zachowania na bind. Najprostszym przykładem jest Maybe. Przypomnijmy, że wszystko, co robi, jest Maybe propagować Nothing na wiążą:

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

Tak powinno być łatwo sobie wyobrazić rozszerzenie dowolny monady z tego zachowania. Wszystko, co robimy, to najpierw sprawdzić na Nothing, a następnie użyć powiązania starej monady. Typ MaybeT robi dokładnie to: owija istniejącą monadę i poprzedza każde wiązanie taką kontrolą. Będziesz musiał również zaimplementować return, zasadniczo owijając wartość w Just, a następnie używając wewnętrznej monady return. Jest też trochę więcej hydrauliki, aby wszystko działało, ale to jest ważna idea.

Można sobie wyobrazić bardzo podobne zachowanie dla Writer: najpierw łączymy wszystkie nowe wyjścia, a potem używamy wiązania starej monady. Jest to zasadniczo zachowanie WriterT. Jest kilka innych szczegółów, ale podstawowy pomysł jest dość prosty i użyteczny.

Transformatory Monad są bardzo popularnym sposobem "łączenia" monad, tak jak chcesz. Istnieją wersje najczęściej używanych monad jako transformatorów, z godnym uwagi wyjątkiem od IO, który zawsze musi znajdować się u podstawy stosu monad. W twoim przypadku zarówno WriterT jak i StateT istnieją i mogą być użyte w twoim programie.

+0

dokładnie tego chcę!Próbowałem i oba z nich (WriterT & StateT) działają dobrze, ty! – Javran

12

Tichon Jelvis daje fajną odpowiedź z transformatorami Monady. Istnieje jednak szybkie rozwiązanie.

modułu w mtlControl.Monad.RWS eksportuje RWS monadę, która jest kombinacją Reader, Writer i State monadach.

Powiązane problemy