1

I want to use Reader in my DSL that I created using a Free monad.

I notice that there is a instance of MonadReader for Free here:

https://hackage.haskell.org/package/free-4.12.1/docs/src/Control-Monad-Free.html#line-264

If I try to call ask inside the program written in my EDSL, I get the the type error "No such instance MonadReader Free MyDSL"

I will eventually want to use other Monads with my DSL such as MonadError and the Logging monad, but the Reader monad is just the first one I've tried.

Joe Hillenbrand
  • 845
  • 9
  • 26
  • 2
    You should post the exact error. I bet it is actually somewhat more specific: I bet it says there's no instance of `MonadReader` for `MyDSL`, *not* for `Free MyDSL` as you claim in the question. That should be quite a strong hint about what is needed for this to work. – Daniel Wagner Sep 17 '15 at 00:24
  • @DanielWagner I'm having trouble reproducing the exact error for various reasons, but the specifics don't really matter. I'm just looking for any example that combines any mtl monad with Free. – Joe Hillenbrand Sep 17 '15 at 00:51
  • You'd need `MyDSL` to be an instance of `MonadReader` for the appropriate environment type. `Free` doesn't *add* a reader effect; it just uses an underlying one. If you can't post your exact code and error message, it's hard to see how we can help you. – dfeuer Sep 17 '15 at 06:45

1 Answers1

3

As you linked above, there is an MonadReader instance for Free:

instance (Functor m, MonadReader e m) => MonadReader e (Free m) where

what this says is that given that m is a Functor and that there is a MonadReader e instance for m, we can also make use of the MonadReader instance inside of Free. But this requires that there already is a MonadReader instance for m which in your case is your DSL Functor. This is normally not what you want because this drastically limits the available choices for your DSL functor, given that it is no longer enough to be a functor and it also has to be a monad.

I would therefore suggest instead of using Free (ReaderT r DSL) a you could just layer it the other way around, i.e. ReaderT r (Free DSL) a, which has the benefit that DSL only has to be a functor. To make this more concrete, and given that you did not state how your DSL looks like, let's use the Teletype DSL example:

data TeletypeF a = GetChar (Char -> a) | PutChar Char a deriving Functor

type Teletype a = Free TeletypeF a

getChar :: Teletype Char
getChar = liftF (GetChar id)

putChar :: Char -> Teletype ()
putChar c = liftF (PutChar c ())

putStrLn :: String -> Teletype ()
putStrLn str = traverse putChar str >> putChar '\n'

runTeletype :: Teletype a -> IO a
runTeletype = foldFree go
  where go (GetChar k) = k <$> IO.getChar
        go (PutChar c k) = IO.putChar c >> return k

putStrLn is a program derived from the DSL primitive PutChar. We can interpret programs by using the IO monad. Now we want to use the ReaderT monad transformer to be able to defer the choice of end-of-line separator in putStrLn. So we proceed as follows:

type TeletypeReader a = ReaderT Char (Free TeletypeF) a

getChar' :: TeletypeReader Char
getChar' = lift getChar

putChar' :: Char -> TeletypeReader ()
putChar' c = lift (putChar c)

putStrLn' :: String -> TeletypeReader ()
putStrLn' str = do
  traverse_ putChar' str
  sep <- ask
  putChar' sep

runTeletypeReader :: Char -> TeletypeReader a -> IO a
runTeletypeReader sep = runTeletype . flip runReaderT sep

And now we can do:

λ> runTeletypeReader '\n' (putStrLn' "Hello" >> putStrLn' "World")
Hello
World

λ> runTeletypeReader ':' (putStrLn' "Hello" >> putStrLn' "World")
Hello:World:
Markus1189
  • 2,829
  • 1
  • 23
  • 32