Comment jouer avec Control.Monad.Writer dans haskell?

97

Je suis nouveau dans la programmation fonctionnelle et j'ai récemment appris à Learn You a Haskell , mais quand j'ai parcouru ce chapitre , je suis resté 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 et je n'ai pas réussi à l'importer dans mon ghci, ce 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, c'était censé être quelque chose comme "newtype Writer wa ..." donc je ne sais pas comment alimenter le constructeur de données et obtenir un Writer.

Je suppose que cela pourrait être un problème lié à la version et ma version ghci est 7.4.1

Javran
la source
2
Pour les exercices, je recommande de ne pas importer la déclaration et de l'écrire vous-même dans le fichier.
sdcvvc
5
J'ai parlé à l'auteur il y a quelque temps, et il a confirmé que la version en ligne du livre était obsolète. Il existe une version plus à jour sur PDF: [ici ]
Electric Coffee
@Electric, j'ai la même question, pourriez-vous s'il vous plaît donner un lien? Votre lien ci-dessus est rompu.
Bulat M.
2
@BulatM. Ici vous allez
Café électrique

Réponses:

127

Le package Control.Monad.Writern'exporte pas le constructeur de données Writer. Je suppose que c'était différent lorsque LYAH a été écrit.

Utilisation de la classe de types MonadWriter dans ghci

Au lieu de cela, vous créez des rédacteurs à l'aide de la writerfonction. Par exemple, dans une session ghci, je peux faire

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

C'est maintenant logNumberune 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 renvoie un écrivain particulier , mais plutôt tout ce qui implémente la MonadWriterclasse de type. Je peux maintenant l'utiliser:

ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
    :: Writer [String] Int

(Entrée en fait entré tout sur une seule ligne). Ici, j'ai spécifié le type d' multWithLogêtre 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 MonadWriterclasse de type? La raison est à voir avec les transformateurs monades. Comme vous l'avez bien compris, le moyen le plus simple d'implémenter Writerest de créer un wrapper de nouveau type 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 vouliez une monade qui ait des capacités de journalisation, mais qui fasse également autre chose - disons qu'elle peut également lire à partir d'un environnement. Vous implémenteriez cela comme

type RW r w a = ReaderT r (Writer w a)

Maintenant, parce que l'écrivain est à l'intérieur du ReaderTtransformateur monade, si vous voulez enregistrer la sortie, vous ne pouvez pas utiliser tell w(car cela ne fonctionne qu'avec des écrivains non emballés) mais vous devez utiliser lift $ tell w, ce qui "soulève" la tellfonction à travers le ReaderTafin qu'elle puisse accéder au monade d'écrivain intérieur. Si vous vouliez des transformateurs à deux couches (disons que vous vouliez également ajouter la gestion des erreurs), vous devez les 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 monade autour d'un écrivain en une instance de writer lui-même. Par exemple,

instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)

c'est-à-dire que si west un monoïde, et mest a MonadWriter w, alors ReaderT r mest également a MonadWriter w. Cela signifie que nous pouvons utiliser la tellfonction directement sur la monade transformée, sans avoir à se soucier de la soulever explicitement à travers le transformateur de monade.

Chris Taylor
la source
31
"Je suppose que c'était différent quand LYAH a été écrit." Droite. Cela a changé avec le mtlpassage de la version majeure 1. * à 2. *, peu de temps après l'écriture de LYAH et RWH. Moment extrêmement malheureux qui a conduit et conduit à beaucoup de confusion chez les débutants.
Daniel Fischer
2
J'utilise maintenant la version 7.8.3 de GHC et j'ai dû importer Control.Monad.Trans.Writer. En plus le type de logNumberc'est logNumber :: (Show a, Monad m) => a -> WriterT [[Char]] m apour moi.
kmikael
@kmikael Vous n'avez probablement pas mtlinstallé la bibliothèque (ce qui signifie probablement que vous avez une installation de base de GHC, comme minGHC, plutôt que la plate-forme Haskell). À partir d'une invite de commande, exécutez cabal updateet cabal install mtlréessayez.
Chris Taylor
Chris, j'utilisais la plate-forme Haskell et le mtl a été installé, je l'ai à nouveau installé et maintenant il semble fonctionner comme dans votre réponse. Je ne sais pas ce qui n'allait pas. Merci.
kmikael
La copie imprimée du livre est correcte à la place. Il comprend un paragraphe expliquant qui writerest utilisé à la place de Writerce dernier, la valeur ctor, n'est pas exporté par le module, alors que le premier l'est, et il peut être utilisé pour créer la même valeur que vous créeriez avec le ctor, mais le fait ne pas autoriser la correspondance de modèle.
Enrico Maria De Angelis le
8

Une fonction appelée "writer" est mise à disposition à la place d'un constructeur "Writer". Changement:

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

à:

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

Marcus
la source
6
Qu'est-ce que cela ajoute aux réponses existantes?
dfeuer
1

J'ai reçu un message similaire en essayant le LYAH "Pour quelques monades en plus" 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 ceci (avec l'inspiration du blog Haskell de Kwang ):

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 exécutable ici

Simon Dowdeswell
la source