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
Odpowiedzi:
Pakiet
Control.Monad.Writer
nie eksportuje konstruktora danychWriter
. 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
byciaWriter [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 implementacjiWriter
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 im
jest aMonadWriter w
,ReaderT r m
to również jestMonadWriter w
. Oznacza to, że możemy użyć tejtell
funkcji bezpośrednio na transformowanej monadzie, bez konieczności zawracania sobie głowy jawnym podnoszeniem jej przez transformator monady.źródło
mtl
przejś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.Control.Monad.Trans.Writer
. Dodatkowo typlogNumber
jestlogNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m a
dla mnie.mtl
zainstalowanej biblioteki (co prawdopodobnie oznacza, że masz podstawową instalację GHC, taką jak minGHC, a nie Platformę Haskell). Z wiersza polecenia biegucabal update
icabal install mtl
, a następnie spróbuj ponownie.writer
jest używany zamiastWriter
tego 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.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])
źródło
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
źródło