2016-04-26 19 views
5

Ich habe zwei Monaden für eine domänenspezifische Sprache geschrieben, die ich entwickle. Die erste ist Lang, die alles enthalten soll, was benötigt wird, um die Sprache Zeile für Zeile analysieren zu können. Ich wusste, ich möchte Leser, Schreiber und Staat, so habe ich die RWS Monade:Applicative kann nicht abgeleitet werden, wenn zwei Monad-Transformer-Stacks kombiniert werden

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 
    ) 

Die zweite ist Repl, die Haskeline mit einem Benutzer interagieren verwendet:

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

Beide scheinen zu arbeiten einzeln (sie kompilieren und ich habe mit ihrem Verhalten in GHCi herumgespielt), aber ich bin nicht in der Lage gewesen, Lang in Repl einzubetten, um Linien vom Benutzer zu analysieren. Die Hauptfrage ist, wie kann ich das tun?

Genauer gesagt, wenn ich Repl schreiben Lang den Weg schließen ich ursprünglich gedacht:

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

Es meist typechecks, aber ich kann Applicative (für Monad und die ganzen Rest erforderlich) nicht ableiten.

Da ich Monod Transformatoren neu bin und REPLs entwerfe, habe ich von Glambda 's Repl.hs und Monad.hs studiert/Fracht-Kult. Ich habe es ursprünglich ausgewählt, weil ich versuchen werde, GADTs auch für meine Ausdrücke zu verwenden. Es enthält ein paar ungewohnte Praktiken, die ich angenommen habe, aber sind völlig offen für Veränderung:

  • newtype + GeneralizedNewtypeDeriving (das ist gefährlich?)
  • MaybeT zu erlauben, die REPL Aufhören mit mzero

Hier ist mein Arbeits Code so weit:

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

Und ein paar versucht, sie zu verlängern. Erstens, einschließlich Lang in Repl wie oben erwähnt:

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’ 

Als nächstes versuchen, nur sie beide auf einmal verwenden:

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

Nicht gezeigt: Ich habe auch versucht, verschiedene Permutationen von lift auf dem ask und putStrLn Anrufe. Schließlich sicher sein dies kein RWS-spezifisches Problem ist habe ich versuchte Lang ohne es zu schreiben:

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

, dass die gleichen eta-reduzieren Fehler gibt.

Also zur Zusammenfassung, die Hauptsache, die ich wissen möchte, ist, wie kombiniere ich diese zwei Monaden? Fehle ich eine offensichtliche Kombination von lift s oder die Anordnung der Transformatorstapel falsch oder in ein tieferes Problem läuft?

Hier sind ein paar möglicherweise im Zusammenhang mit Fragen, die ich betrachten:

Update: meine Hand wellig Verständnis von Monade Transformatoren war die Haupt Problem. Mit RWST statt RWS so LangT zwischen Repl und IO meist löst es eingefügt:

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 
    ) 

Die einzige noch offene Frage ist, ich brauche, um herauszufinden, wie man Repl2 eine Instanz io MonadIO.

Update 2: Alles gut jetzt! Nur benötigt, um MonadTrans zu der Liste der für LangT abgeleiteten Instanzen hinzuzufügen.

+0

'IO' muss sich am unteren Ende des Monad-Transformators befinden, da dort kein' IOT'-Monodentrafo vorhanden ist (http://Stackoverflow.com/questions/13056663/why-is-there-no-io-) Transformator-in-Haskell). Etwas wie 'newtype LangT m a = LangT (RWST .. .. .. m a); newtype Repl a = Repl (MaybeT (InputT (LangT IO)) a) 'könnte für Sie arbeiten. – user2407038

+0

Sie haben Recht, danke! Ich wusste, dass "IO" auf der Unterseite sein muss, aber aus irgendeinem Grund war mir nicht eingefallen, dass der ganze Stapel linear ist. Ich dachte, du könntest einen anderen Typ "auf die Seite legen". Wird die Frage aktualisieren. – Jeff

+0

'LangT' benötigt eine' MonadIO m => MonadIO (LangT m) '-Instanz (die wahrscheinlich abgeleitet werden kann), weil die 'MonadIO m => MonadIO (MaybeT m) -Instanz dies erfordert. – user2407038

Antwort

4

Sie versuchen, die zwei Monaden, eine über der anderen zu komponieren. Aber im Allgemeinen monads don't compose this way. Sehen wir uns eine vereinfachte Version Ihres Falles an. Nehmen wir an, wir haben nur Maybe anstelle von MaybeT ... und Reader statt Lang. So ist die Art Ihrer Monade wäre

Maybe (LangConfig -> a) 

Nun, wenn dies eine Monade, würden wir insgesamt join Funktion haben, die Art

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

haben würde Und hier noch ein Problem kommt: Was passiert, wenn die Argument ist ein Wert Just f wo

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

und für einige Eingangs f kehrt Nothing? Es gibt keinen vernünftigen Weg, wie wir einen sinnvollen Wert von Maybe (LangConfig -> a) von Just f konstruieren könnten. Wir müssen die LangConfig lesen, so dass f entscheiden kann, ob seine Ausgabe Nothing oder Just something ist, aber innerhalb Maybe (LangConfig -> a) können wir entweder Nothing zurückgeben oder LangConfig lesen, nicht beides! Wir können also keine join Funktion haben.

Wenn Sie sorgfältig auf Monade-Transformatoren schauen, sehen Sie, dass es manchmal nur eine Möglichkeit gibt, zwei Monaden zu kombinieren, und es ist nicht ihre naive Zusammensetzung. Insbesondere sind sowohl ReaderT r Maybe a als auch isomorph zu r -> Maybe a. Wie wir bereits gesehen haben, ist das Gegenteil keine Monade.

Also die Lösung für Ihr Problem ist es, Monade Transformatoren anstelle von Monaden zu konstruieren. Sie können entweder beide als Monade Transformatoren:

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

und sie als LangT (ReplT IO) a oder ReplT (LangT IO) a (wie in einem der Kommentare beschrieben, IO hat immer an der Unterseite des Stapels sein). Oder Sie können nur einen von ihnen (den äußeren) als Transformator und einen anderen als Monade haben. Aber da Sie IO verwenden, muss die innere Monade intern IO enthalten.

Beachten Sie, dass zwischen LangT (ReplT IO) a und ReplT (LangT IO) a ein Unterschied besteht. Es ist ähnlich dem Unterschied zwischen StateT s Maybe a und MaybeT (State s) a: Wenn ersteres mit mzero ausfällt, wird weder Ergebnis noch Ausgangszustand produziert. Aber in letzterem schlägt mit mzero fehl, gibt es kein Ergebnis, aber der Zustand bleibt verfügbar.

+1

Danke! Ich denke, dass ich (langsam, endlich) langsam eine Intuition für dieses Zeug bekomme. – Jeff