Jak grać z Control.Monad.Writer w Haskell?

97

Jestem nowy w programowaniu funkcjonalnym i ostatnio uczę się w Learn You a Haskell , ale kiedy przeczytałem ten rozdział , utknąłem w poniższym programie:

import Control.Monad.Writer  

logNumber :: Int -> Writer [String] Int  
logNumber x = Writer (x, ["Got number: " ++ show x])  

multWithLog :: Writer [String] Int  
multWithLog = do  
    a <- logNumber 3  
    b <- logNumber 5  
    return (a*b)

Zapisałem te linie w pliku .hs i nie udało mi się zaimportować go do mojego ghci, który narzekał:

more1.hs:4:15:
    Not in scope: data constructor `Writer'
    Perhaps you meant `WriterT' (imported from Control.Monad.Writer)
Failed, modules loaded: none.

Sprawdziłem typ poleceniem ": info":

Prelude Control.Monad.Writer> :info Writer
type Writer w = WriterT w Data.Functor.Identity.Identity
               -- Defined in `Control.Monad.Trans.Writer.Lazy'

Z mojego punktu widzenia miało to być coś w rodzaju „Newtype Writer Wa ...”, więc nie wiem, jak zasilić konstruktor danych i uzyskać Writer.

Myślę, że może to być problem związany z wersją, a moja wersja ghci to 7.4.1

Javran
źródło
2
Do ćwiczeń odradzam importowanie deklaracji i samodzielne zapisywanie jej w pliku.
sdcvvc
5
Rozmawiałem z autorem jakiś czas temu i potwierdził, że internetowa wersja książki jest nieaktualna. Jest bardziej aktualna wersja w PDF: [tutaj ]
Electric Coffee
@Electric, mam to samo pytanie, czy mógłbyś podać link? Twój powyższy link jest uszkodzony.
Bulat M.,
2
@BulatM. Proszę bardzo
Kawa elektryczna

Odpowiedzi:

127

Pakiet Control.Monad.Writernie 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ą writerfunkcji. Na przykład w sesji ghci mogę zrobić

ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])

Teraz logNumberjest 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 MonadWriterklasę 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 multWithLogbycia 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 MonadWriterco 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 Writerjest 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 ReaderTtransformatora 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” tellfunkcję 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 wjest monoidem i mjest a MonadWriter w, ReaderT r mto również jest MonadWriter w. Oznacza to, że możemy użyć tej tellfunkcji bezpośrednio na transformowanej monadzie, bez konieczności zawracania sobie głowy jawnym podnoszeniem jej przez transformator monady.

Chris Taylor
źródło
31
„Wydaje mi się, że było inaczej, gdy napisano LYAH”. Dobrze. Zmieniło się to wraz z mtlprzejściem z wersji głównej 1. * do 2. *, wkrótce po napisaniu LYAH i RWH. Niezwykle niefortunny moment, który doprowadził i prowadzi do dużego zamieszania wśród początkujących.
Daniel Fischer,
2
Używam teraz GHC w wersji 7.8.3 i musiałem zaimportować Control.Monad.Trans.Writer. Dodatkowo typ logNumberjest logNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m adla mnie.
kmikael
@kmikael Prawdopodobnie nie masz mtlzainstalowanej biblioteki (co prawdopodobnie oznacza, że ​​masz podstawową instalację GHC, taką jak minGHC, a nie Platformę Haskell). Z wiersza polecenia biegu cabal updatei cabal install mtl, a następnie spróbuj ponownie.
Chris Taylor
Chris, korzystałem z platformy Haskell i zainstalowałem mtl, ale zainstalowałem go ponownie i teraz wygląda na to, że działa tak, jak w Twojej odpowiedzi. Nie wiem, co się stało. Dzięki.
kmikael
Zamiast tego wydrukowana kopia książki jest poprawna. Zawiera akapit wyjaśniający, który writerjest używany zamiast Writertego drugiego, wartość ctor, nie jest eksportowana przez moduł, podczas gdy pierwsza jest i może być używana do tworzenia tej samej wartości, którą utworzyłbyś za pomocą ctor, ale tak nie zezwalaj na dopasowywanie wzorców.
Enrico
8

Funkcja o nazwie „pisarz” jest udostępniana zamiast konstruktora „Writer”. Zmiana:

logNumber x = Writer (x, ["Got number: " ++ show x])

do:

logNumber x = writer (x, ["Got number: " ++ show x])

Marcus
źródło
6
Co to dodaje do istniejących odpowiedzi?
dfeuer
1

Otrzymałem podobną wiadomość po wypróbowaniu LYAH „Za kilka monad więcej” przy użyciu internetowego edytora Haskell w repl.it

Zmieniłem import z:

import Control.Monad.Writer

do:

import qualified Control.Monad.Trans.Writer.Lazy as W

Więc mój kod działa teraz tak (zainspirowany blogiem Kwanga Haskell ):

import Data.Monoid
import qualified Control.Monad.Trans.Writer.Lazy as W


output :: String -> W.Writer [String] ()
output x = W.tell [x]


gcd' :: Int -> Int -> W.Writer [String] Int  
gcd' a b  
    | b == 0 = do  
        output ("Finished with " ++ show a)
        return a  
    | otherwise = do  
        output (show a ++ " mod " ++ show b ++ " = " ++ show (a `mod` b))
        gcd' b (a `mod` b)

main :: IO()
main = mapM_ putStrLn $ snd $ W.runWriter (gcd' 8 3) 

Kod jest obecnie dostępny tutaj

Simon Dowdeswell
źródło