Unable to get Application when combining two monoblock transformer stacks

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 .

+5
source share
1 answer

You are trying to make two monads, one on top of the other. But in general, monads do not make up this path . Let's look at a simplified version of your case. Suppose that instead of Lang and Reader instead of Lang we have Maybe instead of MaybeT ... and Reader . So the type of your monad will be

 Maybe (LangConfig -> a) 

Now, if it were a monad, we would have a complete join function that would be of type

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

And here the problem arises: what if the argument is Just f , where

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

and for some input f returns Nothing ? There is no reasonable way to construct the meaningful Maybe (LangConfig -> a) value from Just f . We need to read LangConfig so that f can decide whether its output will be Nothing or Just something , but inside Maybe (LangConfig -> a) we can either return Nothing or read LangConfig , and not both! Therefore, we cannot have such a join function.

If you look closely at the monad transformers, you will see that sometimes there is only one way to combine the two monads, and this is not their naive composition. In particular, both ReaderT r Maybe a and MaybeT (Reader r) a isomorphic to r -> Maybe a . As we saw earlier, the converse is not a monad.

So, the solution to your problem is to build monad transformers instead of monads. You can use both as monad transformers:

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

and use them as LangT (ReplT IO) a or ReplT (LangT IO) a (as described in one comment, IO should always be at the bottom of the stack). Or you can have only one of them (external) as a transformer, and the other as a monad. But since you use IO , the inner monad will have to internally include IO .

Note that there is a difference between LangT (ReplT IO) a and ReplT (LangT IO) a . It is similar to the difference between StateT s Maybe a and MaybeT (State s) a : if the first does not work with mzero , neither the result nor the output state is created. But in the latter case with mzero no result, but the state will remain available.

+4
source

Source: https://habr.com/ru/post/1247981/


All Articles