Haskell Mónada transformador Stack y las firmas de tipos
-
21-09-2019 - |
Pregunta
Estoy intentando crear una pila de transformadores monad y estoy teniendo problemas para conseguir las firmas tipo correcto para mis funciones. (Todavía estoy bastante nuevo en Haskell)
La pila combina múltiples transformadores Statet ya que tengo varios estados que necesito para realizar un seguimiento de los (dos de los cuales podrían ser tupled, pero voy a llegar a eso en un segundo) y una WriterT para el registro.
Esto es lo que tengo hasta ahora:
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
Me gustaría que el popLine
meterse con el estado y las funciones [Line]
xLineNum
afectar al estado Int
. evalr
es el cálculo que se pasará a runPass1
.
cuando cargo el código que se producen errores de que son generalmente de la siguiente variedad:
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 }
Ninguna de las firmas parece ser correcta, pero popline es la primera función de lo que es el único que provoca inmediatamente un error.
Trate de añadir lo que se sugiere en la firma tipo (por ejemplo: popLine :: (MonadState [Line] m) => ...
pero luego él los errores de esta manera:
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)
Siempre parecen llegar este mensaje cada vez que intento hacer algo que no es una variable de tipo. Parece que como (MonadState s m)
bien y error en otra cosa, pero cuando lo intento con un [a]
en lugar de errores s
Es similar a la anterior. (Inicialmente, el [Línea] y se Int tupled en un solo estado, pero que estaba recibiendo este error, así que pensé que iba a tratar de ponerlos en estados separados).
GHC 6.10.4, Kubuntu
Así que, ¿alguien puede decirme lo que está pasando y dar una explicación / muéstrame las firmas de tipos adecuados, o ¿alguien sabe de una buena referencia en esta materia (la única cosa que ha ayudado hasta ahora era "Monad transformadores reductores a paso", pero eso sólo utiliza una función de estado auxiliar y uno Statet)?
Muchas gracias de antemano.
Editar
Aquí está el código de compilación incorporando las sugerencias de JFT y Edward de:
{-# 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)
He combinado incLineNum
y popLine
en nextLine
Todavía necesito para obtener la porción mónada Writer para trabajar, pero creo que sé a dónde ir desde aquí. Gracias, chicos.
Solución
Hubo muchos problemas con el fragmento de código. He arreglado el fragmento de la adición de explicación de lo que estaba roto y añadido algunos consejos de estilo, si usted se preocupa.
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
{- la sustitución de los tipos de importación con las definiciones simples -}
--import Types
type Line = String
type Address = String
type LineNumber = Int
{- No es parte de su pregunta, pero mis 2 centavos aquí ... Digamos que desea cambia la colección para sus estados si no lo hace utilizar un alias de tipo tendrá que cazar a todas partes que lo utilizó. En su lugar sólo cambiar estas definiciones, si es necesario -}
type Lines = [Line]
type Addresses = [Address]
type Messages = [Msg]
data Msg = Error String
| Warning String
{- Lo que es que en Statet Int Int? Nombrar más fácil de leer, razonar acerca y para cambiar. FTW declarativa vamos a utilizar en lugar LineNumber -}
--type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a
{- Vamos a utilizar un tipo "real" para casos se pueden derivar. Desde Pass1 no es una transferencia mónada es decir, no definido como Pass1 m a, No tiene sentido utilizar Statet para la identidad más profunda Statet decir Statet [Dirección] así que vamos a utilizar un Estado [Dirección] -}
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)
{- Vamos a pelar esa pila desde el exterior (lefmost en la declaración) hasta la más interna fue de identidad en su primera declaración. Tenga en cuenta que runWriterT hace falta ser un estado inicial ... El primer parámetro para runStateT (y runState) no es el estado inicial pero la mónada ... así que vamos a voltear! -}
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
{- ahora que la última función no hace lo que quiere, ya que desea ofrecer un registro inicial para anexar a la WriterT. Dado que es un transformador mónada que vamos a hacer algún truco aquí -}
-- 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
{- ¿Tiene la intención de llamar directamente desde un popline Pass1 apilar? Si lo que necesita para "enseñar" Pass1 a ser un "MonadState Lines" Para ello vamos a derivar Pass1 (por eso declaramos con 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
{- Mejor mantener a lo genérico, pero que ahora podríamos haber escrito: popline :: Pass1 (Tal vez Line) -}
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, ahora tengo la Int => LineNumber .... podríamos hacer Pass1 y la instancia de MonadState LineNumber pero LineNumber no debe ser ensuciado con lo que en lugar me gustaría codificar directamente la pendiente y proporcionaría una instancia MonadReader para el consulation si es necesario
check ":t incLineNum and :t curLineNum"
-}
incLineNum = Pass1 . lift $ modify (+1)
curLineNum = Pass1 $ lift get
evalr = do l <- popLine
incLineNum
return l
No se trata de una respuesta de largo aliento, pero mónada y la mónada pila como mejor le está desafiando al principio. He arreglado el código, pero os animo a jugar e inspeccionar los tipos de las diversas funciones para entender lo que está pasando y va a comparar con el original. inferencia de tipos de Haskell significa que por lo general de tipo anotaciones son superfluas (a menos que para eliminar la ambigüedad). En general, el tipo que le daría a la función es menos genérico que era es inferir por lo que es mejor no escribir anotaciones. Tipo de anotación es definitivamente una buena técnica de depuración aunque;)
Saludos
P.S. Real capítulo Mundial Haskell en Mónada transformador es excelente: http://book.realworldhaskell.org/read/monad-transformers.html
Otros consejos
En general, usted encontrará que el código termina mucho más clara utilizando uno Statet con una estructura compuesta más grande de todos los bits de estado que usted necesita. Una buena razón es que cuando usted viene con una hoja de estado se le olvidó que siempre puede crecer la estructura de un campo, y se puede usar el azúcar registro para escribir actualizaciones de campo individuales o dar vuelta a algo así como las fclabels o datos de acceso paquetes para manipular estado.
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