Search code examples
haskellfunctional-programmingmonadsmonad-transformers

Printing inside monad


I'm writing interpreter in haskell. I want to do that with monads. I already created parser, so I have a lot of functions :: State -> MyMonad State, and I can run my program using bind. m >>= inst1 >>= inst2. Everything works perfectly fine, but I have no idea how to create instruction print (or read) in my language with that monad.

I don't want simple, but ugly, solutions like keeping strings to print inside State and printing in main at the end. (What if I have infinity while with print?) I couldn't understand texts from web about that part of monad functionality. There were some explanations like "pack inside IO Monad, it's quite straightforward", but without any working examples. And almost all printing tutorials was about printing in main.

To better explain problem, I prepared minimal "interpreter" example (below). There State is just Int, my monad is AutomatM instructions have type :: Int -> AutomatM Int. So possible instruction is:

inc :: Int -> AutomatM Int
inc x = return (x+1)

I designed it as simple as I could think:

import Control.Applicative
import Control.Monad (liftM, ap)
import Control.Monad.IO.Class (MonadIO(..))
import System.IO

data AutomatM a = AutomatError | Running a

instance Show a => Show (AutomatM a) where
    show (AutomatError) = "AutomatError"
    show (Running a) = "Running " ++ show a

instance Functor AutomatM where
  fmap = liftM

instance Applicative AutomatM where
  pure  = return
  (<*>) = ap


instance Monad AutomatM where
  return x = Running x
  m >>= g = case m of
              AutomatError -> AutomatError
              Running x -> g x
magicPrint x = do
    -- print x         -- How can I make print work?
    -- c <- getLine    -- And if that is as simple as print
    b <- return "1000" -- how can I change constant to c?
    return (x + (read b :: Int))

main = do
    a <- getLine
    print $ (Running (read a :: Int)) >>= (\x -> return (x*2)) >>= magicPrint

My main target is to add print x inside magicPrint. However if it's not harder, it would be nice to have getLine.

I changed state in magicPrint, because print in my language has side effects.

I know that I need something with monad transformers and maybe MonadIO, but it's hard to find any tutorial with simple explanation for beginners. Therefore I would very appreciate extending my minimal code example to work with prints (and maybe getLine/other read Int) and some explanations to that (perhaps with links).

Functor and Aplicative code is based on Defining a new monad in haskell raises no instance for Applicative


Solution

  • In order to create a new type with a Monad instance and access IO form inside of it, you will need to create another monad transformer type called AutomatMT and declare an instance of Monad, MonadTrans, etc. for it. It involves a lot of boilerplate code. I'll try to clarify anything that doesn't make sense.

    import Control.Applicative
    import Control.Monad (liftM, ap)
    import Control.Monad.IO.Class (MonadIO(..))
    import System.IO
    import Control.Monad.Trans.Class (MonadTrans(..), lift)
    
    data AutomatM a = AutomatError | Running a
    
    instance Show a => Show (AutomatM a) where
        show (AutomatError) = "AutomatError"
        show (Running a) = "Running " ++ show a
    
    instance Functor AutomatM where
      fmap = liftM
    
    instance Applicative AutomatM where
      pure  = return
      (<*>) = ap
    
    instance Monad AutomatM where
      return x = Running x
      m >>= g = case m of
                  AutomatError -> AutomatError
                  Running x -> g x
    
    newtype AutomatMT m a = AutomatMT { runAutomatMT :: m (AutomatM a) }
    
    mapAutomatMT :: (m (AutomatM a) -> n (AutomatM b)) -> AutomatMT m a -> AutomatMT n b
    mapAutomatMT f = AutomatMT . f . runAutomatMT
    
    instance (Functor m) => Functor (AutomatMT m) where
        fmap f = mapAutomatMT (fmap (fmap f))
    
    instance MonadTrans AutomatMT where
        lift = AutomatMT . liftM Running
    
    instance (Functor m, Monad m) => Applicative (AutomatMT m) where
        pure = AutomatMT . return . Running
    
        mf <*> mx = AutomatMT $ do
            mb_f <- runAutomatMT mf
            case mb_f of
                AutomatError -> return AutomatError
                Running f  -> do
                    mb_x <- runAutomatMT mx
                    case mb_x of
                        AutomatError -> return AutomatError
                        Running x  -> return (Running (f x))
    
    instance (MonadIO m) => MonadIO (AutomatMT m) where
        liftIO = lift . liftIO
    
    instance (Monad m) => Monad (AutomatMT m) where
        x >>= f = AutomatMT $ do
            v <- runAutomatMT x
            case v of
                AutomatError -> return AutomatError
                Running y  -> runAutomatMT (f y)
    
        fail _ = AutomatMT (return AutomatError)
    
    
    magicPrint :: String -> (AutomatMT IO String)
    magicPrint x = do
      liftIO $ print $ "You gave magic print " ++ x
      let x = "12"
      y <- pure 1
      liftIO $ print y
      pure $ "1"
    
    main = do
      print "Enter some text"
      a <- getLine
      b <- runAutomatMT $ magicPrint a
      pure ()