Je suis novice en programmation fonctionnelle et récemment en apprentissage chez Learn You a Haskell , mais quand j'ai parcouru ce chapitre , je me suis retrouvé coincé avec le programme ci-dessous:
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)
J'ai enregistré ces lignes dans un fichier .hs mais je n'ai pas réussi à l'importer dans mon ghci qui s'est plaint:
more1.hs:4:15:
Not in scope: data constructor `Writer'
Perhaps you meant `WriterT' (imported from Control.Monad.Writer)
Failed, modules loaded: none.
J'ai examiné le type par la commande ": info":
Prelude Control.Monad.Writer> :info Writer
type Writer w = WriterT w Data.Functor.Identity.Identity
-- Defined in `Control.Monad.Trans.Writer.Lazy'
De mon point de vue, cela était supposé être quelque chose comme "newtype Writer w a ..." Je ne comprends donc pas comment alimenter le constructeur de données et obtenir un script Writer.
Je suppose que cela pourrait être un problème lié à la version et ma version de ghci est 7.4.1
Le package Control.Monad.Writer
n'exporte pas le constructeur de données Writer
. J'imagine que c'était différent quand LYAH a été écrit.
Au lieu de cela, vous créez des écrivains à l'aide de la fonction writer
. Par exemple, dans une session ghci je peux faire
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Maintenant, logNumber
est une fonction qui crée des écrivains. Je peux demander son type:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Ce qui me dit que le type inféré n'est pas une fonction qui retourne un écrivain particulier, mais tout ce qui implémente la classe de type MonadWriter
. Je peux maintenant l'utiliser:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Entrée effectivement entrée sur une seule ligne). Ici, j'ai spécifié le type de multWithLog
à Writer [String] Int
. Maintenant je peux l'exécuter:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
Et vous voyez que nous enregistrons toutes les opérations intermédiaires.
Pourquoi se donner la peine de créer la classe de type MonadWriter
? La raison est à voir avec les transformateurs monad. Comme vous l'avez bien compris, la manière la plus simple d'implémenter Writer
est d'utiliser un wrapper newtype au-dessus d'une paire:
newtype Writer w a = Writer { runWriter :: (a,w) }
Vous pouvez déclarer une instance de monade pour cela, puis écrire la fonction
tell :: Monoid w => w -> Writer w ()
qui enregistre simplement son entrée. Supposons maintenant que vous souhaitiez une monade dotée de capacités de journalisation, mais faisant également autre chose - disons qu'elle peut également lire à partir d'un environnement. Vous souhaitez implémenter cela en tant que
type RW r w a = ReaderT r (Writer w a)
Maintenant, parce que l'écrivain est à l'intérieur du transformateur monad ReaderT
, si vous voulez enregistrer la sortie, vous ne pouvez pas utiliser tell w
(car cela fonctionne uniquement avec des écrivains non enveloppés), mais vous devez utiliser lift $ tell w
, qui "soulève" la fonction tell
à travers la ReaderT
so qu'il peut accéder à la monade écrivain intérieur. Si vous vouliez des transformateurs à deux couches (disons que vous vouliez également ajouter le traitement des erreurs), vous devrez utiliser lift $ lift $ tell w
. Cela devient rapidement difficile à manier.
Au lieu de cela, en définissant une classe de type, nous pouvons transformer n'importe quel wrapper de transformateur monad autour d'un scripteur en une instance de scripteur elle-même. Par exemple,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
autrement dit, si w
est un monoïde et m
est un MonadWriter w
, alors ReaderT r m
est également un MonadWriter w
. Cela signifie que nous pouvons utiliser la fonction tell
directement sur la monade transformée, sans avoir à nous soucier de la soulever explicitement à travers le transformateur monad.
J'ai eu un message similaire en essayant LYAH "Pour quelques Monads More" en utilisant l'éditeur en ligne Haskell dans repl.it
J'ai changé l'importation de:
import Control.Monad.Writer
à:
import qualified Control.Monad.Trans.Writer.Lazy as W
Donc, mon code fonctionne maintenant comme suit (avec l'inspiration de Kwang Haskell Blog ):
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)