web-dev-qa-db-fra.com

Comment jouer avec Control.Monad.Writer dans haskell?

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

81
Javran

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.

Utilisation de la classe de types MonadWriter dans ghci

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 le code est-il écrit comme ça?

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.

115
Chris Taylor

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) 

Le code est actuellement utilisable ici

0
Simon Dowdeswell