Domanda

Sto tentando di creare uno stack di trasformatori monade e sto avendo difficoltà a raggiungere il tipo di firme corrette per le mie funzioni. (Sono ancora abbastanza nuovo per Haskell)

Lo stack combina più trasformatori StateT dal momento che ho più stati ho bisogno di tenere traccia di (due delle quali potrebbe essere tupled, ma io riesco anche a che in un secondo) ed un WriterT per la registrazione.

Ecco quello che ho finora:

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

Vorrei che la popLine a pasticciare con lo stato [Line] e le funzioni xLineNum per influenzare lo stato Int. evalr è il calcolo che verrà passato runPass1.

Ogni volta che carico il codice mi imbatto in errori che sono generalmente delle seguenti varietà:

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 }

Nessuno degli firme sembrano essere corrette, ma Popline è la prima funzione in modo che sia l'unico che provoca subito un errore.

I provare ad aggiungere quello che suggerisce nella firma tipo (ad es: popLine :: (MonadState [Line] m) => ... ma poi gli errori in questo modo:

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)

Mi sembra sempre di ottenere questo messaggio ogni volta che provo a fare qualcosa che non è una variabile di tipo. E sembra come (MonadState s m) ok e l'errore su qualcos'altro, ma quando provo con un [a] invece di errori s rende simile a quanto sopra. (Inizialmente la [Linea] e Int sono stati tupled in un singolo stato, ma mi è stato sempre questo errore così ho pensato di provare a metterli in Stati separati).

GHC 6.10.4, Kubuntu

Quindi, qualcuno può dirmi che cosa sta succedendo e dare una spiegazione / mi mostra il tipo di firme di destra, o qualcuno sa di un buon riferimento su questa roba (l'unica cosa che ha aiutato finora è stato "monade Transformers Passo by Step", ma che utilizza solo una funzione aux stato e uno StateT)?

Molte grazie in anticipo.

Modifica
Ecco il codice compilazione accogliendo anche i suggerimenti del 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)

ho combinato incLineNum e popLine in nextLine ho ancora bisogno di ottenere la parte Monade Writer per lavorare, ma credo di sapere dove andare da qui. Grazie, ragazzi.

È stato utile?

Soluzione

Non c'era molti problemi con lo snippet di codice. Ho sistemato il vostro frammento aggiungendo spiegazione di ciò che è stato rotto e aggiunto qualche consiglio di stile se si cura.

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

{- sostituire i tipi di importazione con definizioni -}

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

{-     Non fa parte della tua domanda, ma i miei 2 centesimi qui ...     Dire che si desidera cambiare la collezione per i vostri stati, se non lo fai     utilizzare un tipo alias dovrete cacciare everwhere è stato utilizzato. Invece basta     modificare queste definizioni, se richiesto -}

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


data Msg = Error String
         | Warning String

{-     Che cosa è che Int in StateT Int? Nome più facile da leggere, ragionare su     e per cambiare. FTW dichiarativa usiamo LineNumber invece -}

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

{-     Usiamo un tipo "reale" in modo da istanze possono essere derivate.     Poiché Pass1 non è un trasferimento monade cioè non definita come Pass1 m a,     nessun punto utilizzando StateT per il più profondo StateT cioè StateT [indirizzo] Identità     quindi cerchiamo di utilizzare un solo Stato [indirizzo] -}

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)

{-     Let Peel quella pila dal più esterno (lefmost nella dichiarazione)     fino al più interno era Identity nella dichiarazione originale.     Si noti che runWriterT non ci vuole uno stato di partenza ...     Il primo parametro per runStateT (e runState) non è lo stato iniziale     ma la monade ... quindi cerchiamo di Flip! -}

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

{-     ora che ultima funzione non fa quello che si vuole dal momento che si desidera fornire     un registro iniziale per aggiungere alla con la WriterT.     Dal momento che si tratta di un trasformatore di monade, faremo un po 'di trucco -}

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

{-     Avete intenzione di chiamare Popline direttamente da un Pass1 pila?     Se quindi è necessario "insegnare" Pass1 di essere un "MonadState Lines"     Per fare ciò cerchiamo di derivano Pass1 (è per questo che abbiamo dichiarato 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

{-    Meglio tenere cosa generica, ma che ora potuto scrivere:    Popline :: Pass1 (forse 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 ora ho l'Int => LineNumber ....     potremmo fare Pass1 e l'istanza di MonadState LineNumber ma LineNumber     non dovrebbe essere pasticciato con così invece mi piacerebbe codice direttamente la pendenza     e fornirebbe un esempio MonadReader per la consultazione, se necessario

check ":t incLineNum and :t curLineNum"

-}

incLineNum = Pass1 . lift $ modify (+1)

curLineNum = Pass1 $ lift get

evalr = do l <- popLine
           incLineNum
           return l

V'è una risposta a lungo senza fiato, ma monade e monade pila come vedete stanno sfidando in un primo momento. Ho fissato il codice, ma vi incoraggio a giocare e ispezionare i tipi delle varie funzioni per capire cosa sta succedendo e da confrontare con l'originale. inferenza Haskell significa che normalmente annotazioni di tipo sono superflui (salvo per rimuovere ambiguità). In generale, il tipo ci piacerebbe dare alla funzione è meno generico che era è dedurre, quindi è meglio non scrivere annotate. Tipo di annotazione è definitivamente una buona tecnica di debug però;)

Saluti

P.S. Reale capitolo mondiale Haskell sulla Monade Transformer è eccellente: http://book.realworldhaskell.org/read/monad-transformers.html

Altri suggerimenti

In generale, ci si accorge che il codice si snoda molto più chiaro utilizzando uno StateT con una struttura composita più grande per tutti i bit di stato di cui avete bisogno. Una buona ragione è che quando si arriva con un pezzo di Stato si è dimenticato è sempre possibile far crescere la struttura per un campo, ed è possibile utilizzare lo zucchero di registrazione per scrivere aggiornamenti singolo campo o girare a qualcosa come i fclabels o dati-di accesso pacchetti per manipolare lo stato.

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top