Haskell Monade trasformatore Pila e del tipo di firme
-
21-09-2019 - |
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.
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