Pakiet Control.Monad.Writer
nie eksportuje konstruktora danych Writer
. Wydaje mi się, że było inaczej, kiedy pisano LYAH.
Korzystanie z typeklasy MonadWriter w ghci
Zamiast tego tworzysz pisarzy za pomocą writer
funkcji. Na przykład w sesji ghci mogę zrobić
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Teraz logNumber
jest funkcją, która tworzy pisarzy. Mogę zapytać o jego rodzaj:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Co mówi mi, że wywnioskowany typ nie jest funkcją, która zwraca określony moduł zapisujący, ale raczej wszystkim, co implementuje MonadWriter
klasę typu. Teraz mogę go używać:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Wejście faktycznie wprowadzono wszystko w jednym wierszu). Tutaj określiłem typ multWithLog
bycia Writer [String] Int
. Teraz mogę to uruchomić:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
Widzisz, że rejestrujemy wszystkie operacje pośrednie.
Dlaczego kod jest napisany w ten sposób?
Po MonadWriter
co w ogóle zawracać sobie głowę tworzeniem klasy typu? Powód jest związany z transformatorami monadowymi. Jak prawidłowo zdałeś sobie sprawę, najprostszym sposobem implementacji Writer
jest użycie opakowania nowego typu na parze:
newtype Writer w a = Writer { runWriter :: (a,w) }
Możesz w tym celu zadeklarować instancję monady, a następnie napisać funkcję
tell :: Monoid w => w -> Writer w ()
który po prostu rejestruje swoje dane wejściowe. Teraz załóżmy, że potrzebujesz monady, która ma możliwości rejestrowania, ale robi coś innego - powiedzmy, że może również czytać ze środowiska. Zaimplementowałbyś to jako
type RW r w a = ReaderT r (Writer w a)
Ponieważ program piszący znajduje się wewnątrz ReaderT
transformatora monady, jeśli chcesz rejestrować dane wyjściowe, nie możesz go użyć tell w
(ponieważ działa to tylko z nieopakowanymi programami zapisującymi), ale musisz użyć lift $ tell w
, co „podnosi” tell
funkcję za pomocą ReaderT
, aby uzyskać dostęp do monada wewnętrznego pisarza. Jeśli potrzebujesz transformatorów dwuwarstwowych (powiedzmy, że chciałeś również dodać obsługę błędów), musisz użyć lift $ lift $ tell w
. To szybko staje się nieporęczne.
Zamiast tego, definiując klasę typu, możemy przekształcić dowolne opakowanie transformatora monad wokół programu piszącego w instancję samego programu piszącego. Na przykład,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
to znaczy, jeśli w
jest monoidem i m
jest a MonadWriter w
, ReaderT r m
to również jest MonadWriter w
. Oznacza to, że możemy użyć tej tell
funkcji bezpośrednio na transformowanej monadzie, bez konieczności zawracania sobie głowy jawnym podnoszeniem jej przez transformator monady.