2016-04-26 39 views
5

Ho scritto due monadi per un linguaggio specifico del dominio che sto sviluppando. Il primo è Lang, che dovrebbe includere tutto il necessario per analizzare la lingua riga per riga. Sapevo che vorrei lettore, scrittore, e lo stato, quindi ho usato il RWS monade:Impossibile derivare Applicativo quando si combinano due pile di trasformatori monad

type LangLog = [String] 
type LangState = [(String, String)] 
type LangConfig = [(String, String)] 

newtype Lang a = Lang { unLang :: RWS LangConfig LangLog LangState a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

Il secondo è Repl, che utilizza Haskeline di interagire con un utente:

newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadIO 
    ) 

Entrambi sembrano lavoro individualmente (si compilano e ho giocato con il loro comportamento in GHCi), ma non sono riuscito a incorporare Lang in Repl per analizzare le righe dall'utente. La domanda principale è, come posso farlo?

Più in particolare, se scrivo Repl includere Lang il modo in cui originariamente destinati:

newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadIO 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

per lo più typechecks, ma non riesco a ricavare Applicative (necessario per Monad e tutto il resto).

Da quando sono nuovo ai trasformatori monad e alla progettazione di REPL, ho studiato/cargo-culting da Glambda's Repl.hs e Monad.hs. L'ho originariamente scelto perché proverò ad usare GADT anche per le mie espressioni. Esso comprende un paio di pratiche non familiari, che ho adottato, ma sono totalmente aperti a cambiare:

  • newtype + GeneralizedNewtypeDeriving (è questo pericoloso?)
  • MaybeT per consentire l'uscita dal REPL con mzero

Ecco il mio codice di lavoro finora:

{- LANGUAGE GeneralizedNewtypeDeriving #-} 

module Main where 

import Control.Monad.RWS.Lazy 
import Control.Monad.Trans.Maybe 
import System.Console.Haskeline 

-- Lang monad for parsing language line by line 

type LangLog = [String] 
type LangState = [(String, String)] 
type LangConfig = [(String, String)] 

newtype Lang a = Lang { unLang :: RWS LangConfig LangLog LangState a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

-- Repl monad for responding to user input 

newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadIO 
    ) 

E un paio tentativi di estenderla. In primo luogo, compresi Lang in Repl come detto sopra:

newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) } 
deriving 
    (Functor 
    , Applicative 
    ) 

--  Can't make a derived instance of ‘Functor Repl’ 
--  (even with cunning newtype deriving): 
--  You need DeriveFunctor to derive an instance for this class 
--  In the newtype declaration for ‘Repl’ 
-- 
-- After :set -XDeriveFunctor, it still complains: 
-- 
--  Can't make a derived instance of ‘Applicative Repl’ 
--  (even with cunning newtype deriving): 
--  cannot eta-reduce the representation type enough 
--  In the newtype declaration for ‘Repl’ 

Avanti, cercando di usare appena entrambi contemporaneamente:

-- Repl around Lang: 
-- can't access Lang operations (get, put, ask, tell) 
type ReplLang a = Repl (Lang a) 

test1 :: ReplLang() 
test1 = do 
    liftIO $ putStrLn "can do liftIO here" 
    -- but not ask 
    return $ return() 

-- Lang around Repl: 
-- can't access Repl operations (liftIO, getInputLine) 
type LangRepl a = Lang (Repl a) 

test2 :: LangRepl() 
test2 = do 
    _ <- ask -- can do ask 
    -- but not liftIO 
    return $ return() 

Non illustrato: Ho anche provato varie permutazioni di lift sulla ask e Chiamate putStrLn. Infine, per essere sicuri che questo non è un problema specifico-RWS Ho provato a scrivere Lang senza di essa:

newtype Lang2 a = Lang2 
    { unLang2 :: ReaderT LangConfig (WriterT LangLog (State LangState)) a 
    } 
    deriving 
    (Functor 
    , Applicative 
    ) 

Questo dà lo stesso errore eta-ridurre.

Quindi per ricapitolare, la cosa principale che voglio sapere è come combinare queste due monadi? Mi manca una combinazione ovvia di lift s, o che organizzo la pila del trasformatore sbagliata o che si sta verificando un problema più profondo?

Qui ci sono un paio di domande possibilmente correlate ho guardato:

Aggiornamento: la mia comprensione a mano ondulato di trasformatori monade è stato il principale problema. Utilizzando RWST anziché RWS così LangT è inseribile tra Repl e IO risolve soprattutto è:

newtype LangT m a = LangT { unLangT :: RWST LangConfig LangLog LangState m a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

type Lang2 a = LangT Identity a 

newtype Repl2 a = Repl2 { unRepl2 :: MaybeT (LangT (InputT IO)) a } 
    deriving 
    (Functor 
    , Applicative 
    , Monad 
    -- , MonadIO -- ghc: No instance for (MonadIO (LangT (InputT IO))) 
    , MonadReader LangConfig 
    , MonadWriter LangLog 
    , MonadState LangState 
    ) 

L'unica questione è devo capire come fare Repl2 un'istanza io MonadIO.

Aggiornamento 2: Tutto bene ora! Basta aggiungere MonadTrans all'elenco delle istanze derivate per LangT.

+0

'IO' deve trovarsi nella parte inferiore della pila di trasformatori monad perché non c'è un trasformatore monade' IOT' (http://stackoverflow.com/questions/13056663/why-is-there-no-io- trasformatore-in-haskell). Qualcosa come 'newtype LangT m a = LangT (RWST .. ..m a); newtype Repl a = Repl (MaybeT (InputT (LangT IO)) a) 'potrebbe funzionare per voi. – user2407038

+0

Hai ragione, grazie! Sapevo che 'IO' doveva essere in fondo, ma per qualche motivo non mi era venuto in mente che l'intero stack fosse lineare. Pensavo che potessi mettere un altro tipo di "off to side". Aggiornerà la domanda – Jeff

+0

'LangT' ha bisogno di un'istanza' MonadIO m => MonadIO (LangT m) '(che può probabilmente essere derivata) perché l'istanza' MonadIO m => MonadIO (MaybeT m) 'lo richiede. – user2407038

risposta

4

Stai cercando di comporre le due monadi, una sopra l'altra. Ma in generale monads don't compose this way. Diamo un'occhiata a una versione semplificata del tuo caso. Supponiamo di avere solo Maybe anziché MaybeT ... e Reader anziché Lang. Così il tipo di monade sarebbe

Maybe (LangConfig -> a) 

Ora, se questo fosse una monade, avremmo un join funzione totale, che avrebbe tipo

join :: Maybe (LangConfig -> Maybe (LangConfig -> a)) -> Maybe (LangConfig -> a) 

E qui sorge un problema: cosa succede se la argomento è un valore in cui Just f

f :: LangConfig -> Maybe (LangConfig -> a) 

e per alcuni input f rendimenti Nothing? Non esiste un modo ragionevole per costruire un valore significativo di Maybe (LangConfig -> a) da Just f. Dobbiamo leggere lo LangConfig in modo che f possa decidere se il suo output sarà Nothing o Just something, ma entro Maybe (LangConfig -> a) possiamo o restituire Nothing o leggere LangConfig, non entrambi! Quindi non possiamo avere una tale funzione join.

Se osservate attentamente i trasformatori monad, vedete che a volte c'è solo un modo per combinare due monadi e non è la loro composizione ingenua. In particolare, sia ReaderT r Maybe a e sono isomorfi a r -> Maybe a. Come abbiamo visto prima, il contrario non è una monade.

Quindi la soluzione al problema è costruire trasformatori monad anziché monadi. È possibile avere sia come trasformatori monade:

newtype LangT m a = Lang { unLang :: RWST LangConfig LangLog LangState m a } 
newtype ReplT m a = Repl { unRepl :: MaybeT (InputT m) a } 

e li usa come LangT (ReplT IO) a o ReplT (LangT IO) a (come descritto in uno dei commenti, IO deve sempre essere in fondo alla pila). Oppure puoi avere solo uno di questi (quello esterno) come un trasformatore e un altro come una monade. Ma mentre stai usando IO, la monade interna dovrà includere internamente IO.

Si noti che c'è una differenza tra LangT (ReplT IO) a e ReplT (LangT IO) a. È simile alla differenza tra StateT s Maybe a e MaybeT (State s) a: Se il primo non funziona con mzero, non viene prodotto né lo stato di output né quello di output. Ma in quest'ultimo non riesce con mzero, non vi è alcun risultato, ma lo stato rimarrà disponibile.

+1

Grazie! Penso di essere (lentamente, finalmente) iniziando ad avere un'intuizione per questa roba. – Jeff