Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to nest monads

Tags:

haskell

monads

I have this code in Haskell:

import Control.Monad.Trans.State

simpleState = state (\x -> (x, x + 1))

runUntil :: (s -> Bool) -> State s a -> State s a
runUntil f s = do
    s' <- get
    -- Here I want to print value of s' to console
    if f s'
        then s >> runUntil f s
        else s

main :: IO ()
main = do
    let (x,s) = runState (runUntil (< 10) simpleState) 0
    putStrLn $ "State = " ++ (show s) ++ " Result = " ++ (show x)

I want to print the value of the state on each iteration of runUntil.
If I can't print it in runUntil function where I can do this?

like image 206
alxkolm Avatar asked Dec 03 '13 12:12

alxkolm


2 Answers

Welcome to the wonderful world of Monad Transformers. There's a nice library called the MTL that provides the "monad transformer" equivalents of most monads. By convention, these end with a capital T, so StateT is what we want. Monad transformers have their usual operations and one more, lift, for a StateT that looks like this,

lift :: Monad m => m a -> StateT s m a

Now there's a special class for transformers on top of IO called MonadIO. To use it, we'd do something like. It's similar to just a plain old monad transformer but has the type signature

liftIO :: (MonadIO m, Monad m) => IO a -> m a

 import Control.Monad.State
 import Control.Monad.Trans

 simpleState :: StateT Integer IO ()
 simpleState = modify (+1)

 runUntil :: Show s => (s -> Bool) -> StateT s IO a -> StateT s IO s
 runUntil pred newState = do
    curr <- get
    if pred curr
    then liftIO (print curr) >> newState >> runUntil pred newState
    else return curr

Then to run it, there's a handy set of functions that turn StateT s m a's into s -> (s, a).

main :: IO ()
main = do
  (x,s) <- runStateT (runUntil (< 10) simpleState) 0
  putStrLn $ "State = " ++ (show s) ++ " Result = " ++ (show x)

Notice that now we use bind (the <-) because the result is in IO, it's no longer pure. Monad transformers can be pretty confusing, luckily Real World Haskell has a chapter on them. If you're confused it's worth looking at.

like image 132
Daniel Gratzer Avatar answered Nov 17 '22 01:11

Daniel Gratzer


It looks to me like our print might just be for debugging purposes....

If I am correct, you can print to the terminal from anywhere (even outside of an IO function) using Debug.trace.

Just import Debug and connect to any other value like this

trace (show s') $ 
    if f s'
        then s >> runUntil f s
        else s

(note the signature of trace is String->a->a, so you need the $ followed by another expression

Again, this is only for development code and should be removed from production code (it totally breaks the whole "no side effects" thing)!

like image 38
jamshidh Avatar answered Nov 17 '22 01:11

jamshidh