0

My question relates to the simple interpreter written in in this answer. I already asked a similar question before, which relates to the first non monadic interpreter in the answer behind the link. But there is a second monadic one, to which this question relates.

How could one add IO capabilities to the monadic interpreter (You need to scroll down because the answer contains two variants, the first being non-monadic and the second monadic.)? By this I simply mean adding a statement that uses putStrLn. I'm not that well versed in Haskell yet, but I'm guessing you can just combine the IO monad with the interpreter monad somehow. Can somebody point me in the right direction?

data Stmt
  = Var := Exp                                   
  | While Exp Stmt                                               
  | Seq [Stmt]      
  | Print Exp       -- a print statement
John Smith
  • 2,282
  • 1
  • 14
  • 22
  • 1
    why have you asked this twice? https://stackoverflow.com/questions/53994496/how-to-bring-an-interpreter-to-the-io-monad (You appear to have even accepted an answer to the last one.) – Robin Zigmond Jan 01 '19 at 11:06
  • Possible duplicate of [How to bring an interpreter to the IO monad?](https://stackoverflow.com/questions/53994496/how-to-bring-an-interpreter-to-the-io-monad) – AJF Jan 01 '19 at 11:12
  • didn't I write that? the first question and it's accepted answer relate to the non-monadic parser while this one relates to the monadic parser – John Smith Jan 01 '19 at 11:19
  • 1
    Rather than the IO monad, you may want to use the Writer monad. Then you can do whatever you want with the result afterwards, like printing. – 4castle Jan 01 '19 at 14:38
  • @Robin Zigmond: It would be nice if somebody would provide constructive critique. I hope my edits made it clear, that this is not the same question and that I am also looking for an answer to this question not just to the other one. Should I create a new question copying the monadic parser into it and asking how I can add a print statement? – John Smith Jan 02 '19 at 10:19
  • @JohnSmith I apologise, I misunderstood that this was a different question, from a quick read it looked like it was exactly the same as the other one. You have to admit the difference isn't obvious. Unfortunately I am not able to help, as I am relatively new to Haskell and, while I find it an absolutely fascinating language, am as of yet not capable of answering "deeper" questions like this. I read Haskell questions here on SO mostly for my own learning benefit (although I do try to answer more basic questions which are within my level of understanding). – Robin Zigmond Jan 02 '19 at 10:28
  • @Robin Zigmond: Well it is the _same_ question related to a _different_ parser, so I won't blame you. Next time, I'll reword the whole question. – John Smith Jan 03 '19 at 11:08

1 Answers1

2

A straightforward approach is to change Interp to incorporate IO.

newtype Interp a = Interp { runInterp :: Store -> IO (Either String (a, Store)) }

Then we just need to update the Monad instance, rd, wr, and run for the new internals of Interp by sprinkling in some returns and binds. For example, here’s the new Monad instance:

instance Monad Interp where
  return x = Interp $ \r -> return (Right (x, r))
  i >>= k =
    Interp $ \r -> do
      res <- runInterp i r
      case res of
        Left msg -> return (Left msg)
        Right (x, r') -> runInterp (k x) r'
  fail msg = Interp $ \_ -> return (Left msg)

One of the advantages of having abstracted out Interp in the first place was so that we can make these kinds of changes without modifying the main part of the interpreter (eval and exec) at all.

Anders Kaseorg
  • 3,657
  • 22
  • 35
  • I have been reading about monad transformers, while trying to figure this out. Do you think it might make sense to go that way? It seems (to me the unexperienced reader) that this could actually enable me to use different monads for different purposes (like Reader and Writer) with the same interpreter monad instead of just exposing the whole of IO. – John Smith Jan 03 '19 at 11:10
  • 1
    Sure, you can use monad transformers here. For example, `newtype Interp m a = Interp { runInterp :: Store -> m (Either String (a, Store)) }` (with `m` instead of `IO`) is a monad transformer. You factor the `Either String` part out as `EitherT`, and the `(, Store)` part as `WriterT`. It’s up to you how far you want to go down this path—the ability to keep generalizing your program with more and more complicated abstractions is simultaneously one of Haskell’s greatest strengths and one of Haskell’s greatest weaknesses… – Anders Kaseorg Jan 03 '19 at 11:16
  • Why would you use `WriterT` as a replacement for `(, Store)`? Isn't `Store` somewhat more like a `StateT`? – John Smith Jan 16 '19 at 09:36
  • @JohnSmith You're right, that's what I should have written. – Anders Kaseorg Jan 16 '19 at 17:36