I wrote two monads for the domain-specific language that I am developing. The first is Lang , which should include everything you need to parse the language. I knew that I wanted a reader, writer and state, so I used the RWS 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 )
The second Repl that Haskeline uses to interact with the user:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) a } deriving ( Functor , Applicative , Monad , MonadIO )
Both seem to work individually (they compile, and I played with my behavior in GHCi), but I was not able to insert Lang into Repl to parse lines from the user. The main question is: how can I do this?
In particular, if I write Repl to enable Lang way I originally planned:
newtype Repl a = Repl { unRepl :: MaybeT (InputT IO) (Lang a) } deriving ( Functor , Applicative , Monad , MonadIO , MonadReader LangConfig , MonadWriter LangLog , MonadState LangState )
These are mostly typechecks, but I can't get Applicative (required for Monad and everything else).
Since I am new to monad transformers and designing REPL, I studied / processed loads from Glambda Repl.hs and Monad.hs . I initially chose it because I will also try to use GADT for my expressions. It includes a couple of unfamiliar practices that I have adopted, but are fully open to change:
newtype + GeneralizedNewtypeDeriving (is this dangerous?)MaybeT to allow exit from REPL with mzero
Here is my working code:
{- 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 )
And the couple is trying to extend it. First, including Lang in Repl as above:
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'
Then, trying to just use both of them at once:
-- 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 ()
Not shown: I also tried various lift permutations in the ask and putStrLn calls. Finally, to make sure this is not an RWS related issue, I tried writing Lang without it:
newtype Lang2 a = Lang2 { unLang2 :: ReaderT LangConfig (WriterT LangLog (State LangState)) a } deriving ( Functor , Applicative )
This gives the same eta-reduce error.
So, to repeat, the main thing I want to know is how do I combine these two monads? Did I miss the obvious combination of lift s, or did I install the transformer stack incorrectly, or ran into some deeper problem?
Here are some possible questions that I have addressed:
Update: My manual research on monad transformers was a major issue. Using RWST instead of RWS , so LangT can be inserted between Repl and IO , basically solves it:
newtype LangT ma = LangT { unLangT :: RWST LangConfig LangLog LangState ma } 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 )
The only remaining problem is I need to figure out how to make a Repl2 instance of io MonadIO .
Update 2: All is well now! You just need to add MonadTrans to the list of instances obtained for LangT .