Use two monads without a transformer

To understand how to use monad transformers, I wrote the following code without it. It reads the standard input line by line and displays each line in reverse order until an empty line is encountered. It also counts lines using State and displays the total at the end.

 import Control.Monad.State main = print =<< fmap (`evalState` 0) go where go :: IO (State Int Int) go = do l <- getLine if null l then return get else do putStrLn (reverse l) -- another possibility: fmap (modify (+1) >>) go rest <- go return $ do modify (+1) rest 

I wanted to add the current line number before each line. I was able to do this with StateT :

 import Control.Monad.State main = print =<< evalStateT go 0 where go :: StateT Int IO Int go = do l <- lift getLine if null l then get else do n <- get lift (putStrLn (show n ++ ' ' : reverse l)) modify (+1) go 

My question is: how to do the same in the version without monad transformers?

+6
source share
3 answers

The problem you are facing is that manually deploying StateT s IO a is s -> IO (s, a) , not IO (s -> (s, a)) ! When you have this understanding, it’s pretty easy to see how to do it:

 go :: Int -> IO (Int, Int) go s = do l <- getLine if null l then return (s, s) else do putStrLn (show s ++ ' ' : reverse l) go (s+1) 
+10
source

You just need to start calculating the accumulated states in each row. This is O (n²) time, but since your first program already uses O (n) space, this is not too scary. Of course, StateT approach StateT excellent in every way! If you really want to do this “manually” and not pay a price for efficiency, simply manage the state manually, rather than create a state transformer at all. You really do not get any benefit by using State instead of Int in the first program.

+2
source

Perhaps this is what you are looking for?

 main = print =<< fmap (`evalState` 0) (go get) where go :: State Int Int -> IO (State Int Int) go st = do l <- getLine if null l then return (st >>= \_ -> get) else do let ln = evalState st 0 putStrLn(show ln ++ ' ' : reverse l) go (st >>= \_ -> modify (+1) >>= \_ -> get) 

The idea here is to make a recursive go tail, creating a state calculation that you can then evaluate at each step.

EDIT

This version will tie the size of the state calculation to a constant size, although with lazy evaluation, when the previous state calculation is mandatory, we should be able to reuse it without re-evaluation, so I assume that they are essentially the same ...

 main = print =<< fmap (`evalState` 0) (go get) where go :: State Int Int -> IO (State Int Int) go st = do l <- getLine if null l then return st else do let ln = evalState st 0 putStrLn(show ln ++ ' ' : reverse l) go (modify (\s -> s+ln+1) >>= \_ -> get) 
+1
source

All Articles