Pergunta

Estou tentando criar uma pilha de transformadores da Monad e estou tendo problemas para obter as assinaturas do tipo correto para minhas funções. (Ainda sou muito novo em Haskell)

A pilha combina vários transformadores de estatísticas, pois tenho vários estados que preciso acompanhar (dois dos quais podem ser explodidos, mas vou chegar a isso em um segundo) e um escritor para o registro.

Aqui está o que tenho até agora:

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

Eu gostaria do popLine para mexer com o [Line] estado e o xLineNum funções para afetar o Int Estado. evalr é o cálculo que será passado para runPass1.

Sempre que carrego o código, eu tenho erros que geralmente são da variedade a seguir:

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 }

Nenhuma das assinaturas parece correta, mas o PopLine é a primeira função, por isso é o único que imediatamente causa um erro.

Eu tento adicionar o que sugere na assinatura do tipo (por exemplo: popLine :: (MonadState [Line] m) => ... Mas então erros assim:

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)

Eu sempre pareço receber essa mensagem sempre que tento fazer algo que não é uma variável de tipo. Parece gostar (MonadState s m) ok e erro em outra coisa, mas quando eu tento com um [a] ao invés de s erros semelhantes ao acima. (Inicialmente, a [linha] e int foram exploradas em um único estado, mas eu estava recebendo esse erro, então pensei em tentar colocá -los em estados separados).

GHC 6.10.4, Kubuntu

Então, alguém pode me dizer o que está acontecendo e dar uma explicação / me mostrar as assinaturas do tipo certo, ou alguém sabe de uma boa referência sobre essas coisas (a única coisa que ajudou até agora foi "Monad Transformers passo a passo" , mas isso apenas usa uma função de estado aux e um statet)?

Muito obrigado antecipadamente.

Editar
Aqui está o código de compilação que incorpora as sugestões de JFT e 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)

eu combinei incLineNum e popLine em nextLine Ainda preciso fazer com que a parte do escritor funcione, mas acho que sei para onde ir daqui. Obrigado rapazes.

Foi útil?

Solução

Houve muitos problemas com o seu snippet de código. Corrigi seu snippet adicionando explicação sobre o que foi quebrado e adicionei alguns conselhos de estilo, se você se importa.

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

{ - Substituindo seus tipos de importação por definições simples -}

--import Types
type Line       = String
type Address    = String
type LineNumber = Int

{- não faz parte da sua pergunta, mas meus 2 centavos aqui ... diga que você deseja alterar a coleção para seus estados se não usar um pseudônimo de tipo, terá que caçar sempre onde o usou. Em vez disso, basta alterar essas definições, se necessário -}

type Lines     = [Line]
type Addresses = [Address]
type Messages  = [Msg]


data Msg = Error String
         | Warning String

{- O que é isso no statet int? Nomeie mais fácil de ler, raciocinar e mudar. FTW declarativo Vamos usar o linho em vez disso -}

--type Pass1 a = WriterT [Msg] (StateT Int (StateT [Line] (StateT [Address] Identity))) a

{- Vamos usar um tipo "real" para que as instâncias possam ser derivadas. Como o PASS1 não é uma transferência de Monad, ou seja, não definida como Pass1 Ma, não há sentido em usar o Statet para a identidade mais profunda do statet statet [endereço], então vamos usar um estado [endereço] -}

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 descascar que empilham do exterior (Left mais na declaração) até o mais interno foi a identidade em sua declaração original. Observe que o RunWritert não toma um estado inicial ... O primeiro parâmetro para RunStatet (e RunState) não é o estado inicial, mas a Mônada ... então vamos dar um passo! -}

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

{- Agora essa última função não faz o que você deseja, pois deseja fornecer um log inicial para anexar com o Writert. Como é um transformador de Monad, faremos algum truque aqui -}

-- 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

{- você pretende chamar o popline diretamente de uma pilha Pass1? Nesse caso, você precisa "ensinar" o Pass1 para ser um "Monadstate Lines" para fazer isso, vamos derivar o pass1 (é por isso que o declaramos com o 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

{ - melhor mantenha a coisa genérica, mas agora poderíamos ter escrito: popline :: pass1 (talvez linha) -}

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, agora eu recebo o Int => linenumber .... poderíamos fazer o Pass1 e a instância do MonadState LineNumber, mas o linho não deve ser mexido, então eu codificaria diretamente a inclinação e forneceria uma instância do MonadReader para consultório, se necessário

check ":t incLineNum and :t curLineNum"

-}

incLineNum = Pass1 . lift $ modify (+1)

curLineNum = Pass1 $ lift get

evalr = do l <- popLine
           incLineNum
           return l

Lá está uma resposta longa e fiada, mas a mônada e a mônada que você vê são desafiadores a princípio. Corrigi o código, mas encorajo você a jogar e inspecionar os tipos de várias funções para entender o que está acontecendo e a comparar com o seu original. A inferência do tipo de Haskell significa que as anotações de tipo geralmente são supérfluas (a menos que remova a ambiguidade). Em geral, o tipo que daríamos à função é menos genérico que foi inferido, por isso é melhor não digitar anotar. A anotação do tipo é definitivamente uma boa técnica de depuração;)

Felicidades

PS Real World Haskell Capítulo no Monad Transformer é excelente:http://book.realworldhaskell.org/read/monad-transformers.html

Outras dicas

Em geral, você encontrará que o código acaba muito mais claro usando um statet com uma estrutura composta maior para todos os bits de estado que você precisa. Uma boa razão é que, quando você cria um estado de estado, esqueceu, sempre pode cultivar a estrutura em um campo e pode usar o açúcar gravado para escrever atualizações de campo único ou se transformar em algo como os fclabels ou o accessador de dados pacotes para manipular o 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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top