Haskell Monad Transformer Stack et Type Signatures
-
21-09-2019 - |
Question
Je cherche à créer une pile de transformateurs monade et ai du mal à obtenir les signatures de type correct pour mes fonctions. (Je suis encore assez nouveau pour Haskell)
La pile combine plusieurs transformateurs de StateT depuis que j'ai plusieurs états dont j'ai besoin de garder une trace de (dont deux pourraient être uplées, mais je vais y revenir dans une seconde) et un WriterT pour l'enregistrement.
Voici ce que j'ai à ce jour:
module Pass1 where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map
import Types
data Msg = Error String
| Warning String
type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a
runPass1 addrs instrs msgs = runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)
--popLine :: (MonadState s m) => m (Maybe s)
--popLine :: (Monad m) => StateT [Line] m (Maybe Line)
popLine :: (MonadState s m) => m (Maybe Line)
popLine = do
ls <- get
case ls of
x:xs -> do
put xs
return $ Just x
[] -> return Nothing
incLineNum :: (Num s, MonadState s m) => m ()
incLineNum = do
ln <- get
put $ ln + 1
curLineNum :: (MonadState s m) => m s
curLineNum = do
ln <- get
return ln
evalr = do l <- popLine
--incLineNum
return l
Je voudrais que le popLine
au mess avec l'état [Line]
et les fonctions de xLineNum
d'affecter l'état de Int
. evalr
est le calcul qui sera transmis à runPass1
.
Chaque fois que je charge le code que je lance dans des erreurs qui sont généralement de la variété suivante:
Pass1.hs:23:14:
No instance for (MonadState [t] m)
arising from a use of `get' at Pass1.hs:23:14-16
Possible fix: add an instance declaration for (MonadState [t] m)
In a stmt of a 'do' expression: ls <- get
In the expression:
do ls <- get
case ls of {
x : xs -> do ...
[] -> return Nothing }
In the definition of `popLine':
popLine = do ls <- get
case ls of {
x : xs -> ...
[] -> return Nothing }
Pass1.hs:22:0:
Couldn't match expected type `s' against inferred type `[Line]'
`s' is a rigid type variable bound by
the type signature for `popLine' at Pass1.hs:21:23
When using functional dependencies to combine
MonadState [Line] m,
arising from a use of `get' at Pass1.hs:23:14-16
MonadState s m,
arising from the type signature for `popLine'
at Pass1.hs:(22,0)-(28,31)
When generalising the type(s) for `popLine'
Pass1.hs:23:14:
Could not deduce (MonadState [Line] m)
from the context (MonadState s m)
arising from a use of `get' at Pass1.hs:23:14-16
Possible fix:
add (MonadState [Line] m) to the context of
the type signature for `popLine'
or add an instance declaration for (MonadState [Line] m)
In a stmt of a 'do' expression: ls <- get
In the expression:
do ls <- get
case ls of {
x : xs -> do ...
[] -> return Nothing }
In the definition of `popLine':
popLine = do ls <- get
case ls of {
x : xs -> ...
[] -> return Nothing }
Aucune des signatures semblent corrects, mais popeline est la première fonction il est le seul qui provoque immédiatement une erreur.
J'essaie d'ajouter ce qu'il suggère dans la signature de type (par exemple: popLine :: (MonadState [Line] m) => ...
mais il des erreurs comme ceci:
Pass1.hs:21:0:
Non type-variable argument in the constraint: MonadState [Line] m
(Use -XFlexibleContexts to permit this)
In the type signature for `popLine':
popLine :: (MonadState [Line] m) => m (Maybe Line)
Il me semble toujours d'obtenir ce message chaque fois que j'essaie de faire quelque chose qui est pas une variable de type. Il semble que (MonadState s m)
ok et erreur sur quelque chose d'autre, mais quand je l'essayer avec un [a]
au lieu d'erreurs s
de semblable à ce qui précède. (Dans un premier temps le dans un état unique [ligne] et Int ont été uplées, mais je recevais cette erreur, donc je pensais que je vais essayer de les mettre dans des états séparés).
GHC 6.10.4, Kubuntu
Alors, quelqu'un peut me dire ce qui se passe et donner une explication / me montrer les signatures de type droit, ou personne ne sait d'une bonne référence sur ce genre de choses (la seule chose qui a aidé à ce jour était « Monad Transformateurs étape par étape », mais qui utilise une seule fonction publique et aux StateT un)?
BeaucoupMerci à l'avance.
Modifier Voici le code de compilation intégrant les suggestions de son JFT et Edward:
{-# LANGUAGE GeneralizedNewtypeDeriving #-} -- needed for: deriving (Functor,Monad)
{-# LANGUAGE MultiParamTypeClasses #-} -- needed for: MonadState instance
{-# LANGUAGE FlexibleContexts #-} -- needed for: (MonadState PassState m) => ...
module Pass1 where
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import Types
type Lines = [Line]
type Addresses = [Address]
type LineNum = Int
type Messages = [Msg]
data Msg = Error String
| Warning String
data PassState = PassState { passLineNum :: LineNum
, passLines :: Lines
, passAddresses :: Addresses
}
newtype Pass1 a = Pass1 { unPass1 :: WriterT Messages (State PassState) a
}
deriving (Functor,Monad)
instance MonadState PassState Pass1 where
get = Pass1 . lift $ get
put s = Pass1 . lift $ put s
runPass1 :: PassState -> Pass1 a -> ((a, Messages), PassState)
runPass1 state = flip runState state .
runWriterT .
unPass1
curLineNum :: (MonadState PassState m) => m LineNum
curLineNum = do
state <- get
return $ passLineNum state
nextLine :: (MonadState PassState m) => m (Maybe Line)
nextLine = do
state <- get
let c = passLineNum state
let l = passLines state
case l of
x:xs -> do
put state { passLines = xs, passLineNum = (c+1) }
return $ Just x
_ -> return Nothing
evalr :: Pass1 (Maybe Line,LineNum)
evalr = do
l <- nextLine
c <- curLineNum
--tell $ Warning "hello"
return (l,c)
I combiné incLineNum
et popLine
en nextLine
je dois encore obtenir la partie Writer monade au travail, mais pense que je sais où aller d'ici. Merci, les gars.
La solution
Il y avait beaucoup de problèmes avec l'extrait de code. Je fixe votre extrait en ajoutant des explications à ce qui a été cassé et a ajouté quelques conseils de style si vous aimez.
module Pass1_JFT where
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer
import Data.Maybe
import qualified Data.Map as Map
{- remplacer vos types d'importation avec de simples définitions -}
--import Types
type Line = String
type Address = String
type LineNumber = Int
{- Ne fait pas partie de votre question, mais mes 2 cents ici ... Dites que vous voulez changer la collection pour vos états si vous ne utiliser un alias de type que vous devrez chasser everwhere vous l'avez utilisé. Au lieu de cela seulement modifier ces définitions si nécessaire -}
type Lines = [Line]
type Addresses = [Address]
type Messages = [Msg]
data Msg = Error String
| Warning String
{- Qu'est-ce que Int dans StateT Int? Nommez plus facile à lire, à propos de la raison et de changer. Déclarative FTW Utilisons LineNumber à la place -}
--type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a
{- Utilisons un type « réel » si les instances peuvent être dérivées. Depuis Pass1 constitue pas un transfert de monade à-dire non défini comme un Pass1 m, aucun point à l'aide StateT pour le plus profond StateT à savoir StateT [Adresse] Identité donc nous allons simplement utiliser un Etat [Adresse] -}
newtype Pass1 a = Pass1 {
unPass1 :: WriterT Messages (StateT LineNumber (StateT Lines (State Addresses))) a
}
deriving (Functor,Monad)
--runIdentity (runStateT (runStateT (runStateT (runWriterT msgs) 1) instrs) addrs)
{- Nous allons peler que la pile à partir de la plus externe (lefmost dans la déclaration) jusqu'à la plus interne était identité dans votre déclaration d'origine. Notez que runWriterT ne prend pas un état de départ ... Le premier paramètre pour runStateT (et runState) ne soit pas l'état initial mais la monade ... alors laissez Renversons! -}
runPass1' :: Addresses -> Lines -> Messages -> Pass1 a -> ((((a, Messages), LineNumber), Lines), Addresses)
runPass1' addrs instrs msgs = flip runState addrs .
flip runStateT instrs .
flip runStateT 1 .
runWriterT . -- then get process the WriterT (the second outermost)
unPass1 -- let's peel the outside Pass1
{- maintenant que la dernière fonction ne fait pas ce que vous voulez puisque vous voulez fournir un journal initial à ajouter à la WriterT. Comme il est un transformateur de monade nous ferons une astuce ici -}
-- I keep the runStateT convention for the order of the arguments: Monad then state
runWriterT' :: (Monad m,Monoid w) => WriterT w m a -> w -> m (a,w)
runWriterT' writer log = do
(result,log') <- runWriterT writer
-- let's use the monoid generic append in case you change container...
return (result,log `mappend` log')
runPass1 :: Addresses -> Lines -> Messages -> Pass1 a -> ((((a, Messages), LineNumber), Lines), Addresses)
runPass1 addrs instrs msgs = flip runState addrs .
flip runStateT instrs .
flip runStateT 1 .
flip runWriterT' msgs . -- then get process the WriterT (the second outermost)
unPass1 -- let's peel the outside Pass1
{- Avez-vous l'intention d'appeler directement popeline à partir d'une pile Pass1? Si oui, vous devez « enseigner » Pass1 être un « Lignes MonadState » Pour ce faire, nous allons Pass1 dériver (c'est la raison pour laquelle nous avons déclaré avec newtype!) -}
instance MonadState Lines Pass1 where
-- we need to dig inside the stack and "lift" the proper get
get = Pass1 . lift . lift $ get
put s = Pass1 . lift . lift $ put s
{- Mieux vaut garder chose générique, mais nous pourrions maintenant avoir écrit: popeline :: Pass1 (Peut-être la ligne) -}
popLine :: (MonadState Lines m) => m (Maybe Line)
popLine = do
ls <- get
case ls of
x:xs -> do
put xs
return $ Just x
[] -> return Nothing
{- Ok maintenant je Int => LineNumber .... nous pourrions faire Pass1 et instance de MonadState LineNumber mais LineNumber ne devrait pas être sali avec de sorte qu'au lieu je voudrais coder directement le Incline et fournirait une instance MonadReader pour consulation si nécessaire
check ":t incLineNum and :t curLineNum"
-}
incLineNum = Pass1 . lift $ modify (+1)
curLineNum = Pass1 $ lift get
evalr = do l <- popLine
incLineNum
return l
Il est une réponse de longue haleine, mais monade et pile monade que vous voyez sont difficile au début. Je fixe le code mais je vous encourage à jouer et inspecter les types des différentes fonctions de comprendre ce qui se passe et à comparer à l'original. L'inférence de type de Haskell signifie que généralement de type annotations sont superflues (à moins d'éliminer l'ambiguïté). En général, le type que nous donnerions à la fonction est moins générique qui était est infère il est donc préférable de ne pas taper annoter. annotation de type est définitivement une bonne technique de débogage bien;)
Vive
P.S. chapitre Real World Haskell sur Monad Transformer est excellente: http://book.realworldhaskell.org/read/monad-transformers.html
Autres conseils
En général, vous trouverez que le code serpente beaucoup plus claire à l'aide d'un StateT avec une structure composite plus grande pour tous les bits d'état que vous avez besoin. Une bonne raison est que lorsque vous venez avec un morceau de l'état que vous avez oublié, vous pouvez toujours développer la structure par un champ, et vous pouvez utiliser le sucre d'enregistrement pour écrire des mises à jour sur le terrain unique ou se tourner vers quelque chose comme les fclabels ou données accesseur paquets pour manipuler l'état.
data PassState = PassState { passLine :: Int, passLines :: [Line] }
popLine :: MonadState PassState m => m (Maybe Line).
popLine = do
state <- get
case passLines state of
x:xs -> do
put state { passLines = xs }
return (Just x)
_ -> return Nothing