The Maybe monad has a bind function that is strict in its arguments:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
λ (undefined >>= undefined :: Maybe ()) `seq` ()
*** Exception: Prelude.undefined
λ (undefined >>= undefined :: IO ()) `seq` ()
()
λ import Control.Monad.State.Strict
λ (undefined >>= undefined :: State () ()) `seq` ()
()
m >>= k = StateT $ \ s -> do
(a, s') <- runStateT m s
runStateT (k a) s'
λ import Control.Monad.State.Strict
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
*** Exception: Prelude.undefined
λ import Control.Monad.State.Lazy
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
()
λ import Control.Monad.State.Strict
λ import Debug.Trace
λ runStateT ((trace "Hi" $ return ()) >> return undefined :: State () ()) () `seq` ()
Hi
()
λ import Control.Concurrent
λ forkIO $ return undefined
ThreadId 534
λ forkIO $ return $! undefined
<interactive>: Prelude.undefined
ThreadId 580
The IO monad however has a non-strict bind function:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
λ (undefined >>= undefined :: Maybe ()) `seq` ()
*** Exception: Prelude.undefined
λ (undefined >>= undefined :: IO ()) `seq` ()
()
λ import Control.Monad.State.Strict
λ (undefined >>= undefined :: State () ()) `seq` ()
()
m >>= k = StateT $ \ s -> do
(a, s') <- runStateT m s
runStateT (k a) s'
λ import Control.Monad.State.Strict
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
*** Exception: Prelude.undefined
λ import Control.Monad.State.Lazy
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
()
λ import Control.Monad.State.Strict
λ import Debug.Trace
λ runStateT ((trace "Hi" $ return ()) >> return undefined :: State () ()) () `seq` ()
Hi
()
λ import Control.Concurrent
λ forkIO $ return undefined
ThreadId 534
λ forkIO $ return $! undefined
<interactive>: Prelude.undefined
ThreadId 580
And so does the strict State monad:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
λ (undefined >>= undefined :: Maybe ()) `seq` ()
*** Exception: Prelude.undefined
λ (undefined >>= undefined :: IO ()) `seq` ()
()
λ import Control.Monad.State.Strict
λ (undefined >>= undefined :: State () ()) `seq` ()
()
m >>= k = StateT $ \ s -> do
(a, s') <- runStateT m s
runStateT (k a) s'
λ import Control.Monad.State.Strict
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
*** Exception: Prelude.undefined
λ import Control.Monad.State.Lazy
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
()
λ import Control.Monad.State.Strict
λ import Debug.Trace
λ runStateT ((trace "Hi" $ return ()) >> return undefined :: State () ()) () `seq` ()
Hi
()
λ import Control.Concurrent
λ forkIO $ return undefined
ThreadId 534
λ forkIO $ return $! undefined
<interactive>: Prelude.undefined
ThreadId 580
It's easy to see in the source code:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
λ (undefined >>= undefined :: Maybe ()) `seq` ()
*** Exception: Prelude.undefined
λ (undefined >>= undefined :: IO ()) `seq` ()
()
λ import Control.Monad.State.Strict
λ (undefined >>= undefined :: State () ()) `seq` ()
()
m >>= k = StateT $ \ s -> do
(a, s') <- runStateT m s
runStateT (k a) s'
λ import Control.Monad.State.Strict
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
*** Exception: Prelude.undefined
λ import Control.Monad.State.Lazy
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
()
λ import Control.Monad.State.Strict
λ import Debug.Trace
λ runStateT ((trace "Hi" $ return ()) >> return undefined :: State () ()) () `seq` ()
Hi
()
λ import Control.Concurrent
λ forkIO $ return undefined
ThreadId 534
λ forkIO $ return $! undefined
<interactive>: Prelude.undefined
ThreadId 580
To reduce m >>= k
to Weak Head Normal Form, you only need to go as far as the StateT
constructor; you don't need to touch any of the parameters. I suppose something similar happens with IO.
What's so strict then about the IO monad and the strict State monad? Their run functions. This:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
λ (undefined >>= undefined :: Maybe ()) `seq` ()
*** Exception: Prelude.undefined
λ (undefined >>= undefined :: IO ()) `seq` ()
()
λ import Control.Monad.State.Strict
λ (undefined >>= undefined :: State () ()) `seq` ()
()
m >>= k = StateT $ \ s -> do
(a, s') <- runStateT m s
runStateT (k a) s'
λ import Control.Monad.State.Strict
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
*** Exception: Prelude.undefined
λ import Control.Monad.State.Lazy
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
()
λ import Control.Monad.State.Strict
λ import Debug.Trace
λ runStateT ((trace "Hi" $ return ()) >> return undefined :: State () ()) () `seq` ()
Hi
()
λ import Control.Concurrent
λ forkIO $ return undefined
ThreadId 534
λ forkIO $ return $! undefined
<interactive>: Prelude.undefined
ThreadId 580
As opposed to this:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
λ (undefined >>= undefined :: Maybe ()) `seq` ()
*** Exception: Prelude.undefined
λ (undefined >>= undefined :: IO ()) `seq` ()
()
λ import Control.Monad.State.Strict
λ (undefined >>= undefined :: State () ()) `seq` ()
()
m >>= k = StateT $ \ s -> do
(a, s') <- runStateT m s
runStateT (k a) s'
λ import Control.Monad.State.Strict
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
*** Exception: Prelude.undefined
λ import Control.Monad.State.Lazy
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
()
λ import Control.Monad.State.Strict
λ import Debug.Trace
λ runStateT ((trace "Hi" $ return ()) >> return undefined :: State () ()) () `seq` ()
Hi
()
λ import Control.Concurrent
λ forkIO $ return undefined
ThreadId 534
λ forkIO $ return $! undefined
<interactive>: Prelude.undefined
ThreadId 580
Notice how in the strict case putting the expression in WHNF with seq
is enough to trigger the exception.
Of course, with IO we don't have a explicit run function (one that isn't evil, I mean). We just put the IO action into main
.
Notice however that even the strict State monad is not too strict:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
λ (undefined >>= undefined :: Maybe ()) `seq` ()
*** Exception: Prelude.undefined
λ (undefined >>= undefined :: IO ()) `seq` ()
()
λ import Control.Monad.State.Strict
λ (undefined >>= undefined :: State () ()) `seq` ()
()
m >>= k = StateT $ \ s -> do
(a, s') <- runStateT m s
runStateT (k a) s'
λ import Control.Monad.State.Strict
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
*** Exception: Prelude.undefined
λ import Control.Monad.State.Lazy
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
()
λ import Control.Monad.State.Strict
λ import Debug.Trace
λ runStateT ((trace "Hi" $ return ()) >> return undefined :: State () ()) () `seq` ()
Hi
()
λ import Control.Concurrent
λ forkIO $ return undefined
ThreadId 534
λ forkIO $ return $! undefined
<interactive>: Prelude.undefined
ThreadId 580
This doesn't trigger an exception, either:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
λ (undefined >>= undefined :: Maybe ()) `seq` ()
*** Exception: Prelude.undefined
λ (undefined >>= undefined :: IO ()) `seq` ()
()
λ import Control.Monad.State.Strict
λ (undefined >>= undefined :: State () ()) `seq` ()
()
m >>= k = StateT $ \ s -> do
(a, s') <- runStateT m s
runStateT (k a) s'
λ import Control.Monad.State.Strict
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
*** Exception: Prelude.undefined
λ import Control.Monad.State.Lazy
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
()
λ import Control.Monad.State.Strict
λ import Debug.Trace
λ runStateT ((trace "Hi" $ return ()) >> return undefined :: State () ()) () `seq` ()
Hi
()
λ import Control.Concurrent
λ forkIO $ return undefined
ThreadId 534
λ forkIO $ return $! undefined
<interactive>: Prelude.undefined
ThreadId 580
But this does:
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
λ (undefined >>= undefined :: Maybe ()) `seq` ()
*** Exception: Prelude.undefined
λ (undefined >>= undefined :: IO ()) `seq` ()
()
λ import Control.Monad.State.Strict
λ (undefined >>= undefined :: State () ()) `seq` ()
()
m >>= k = StateT $ \ s -> do
(a, s') <- runStateT m s
runStateT (k a) s'
λ import Control.Monad.State.Strict
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
*** Exception: Prelude.undefined
λ import Control.Monad.State.Lazy
λ runStateT (undefined >> return () :: State () ()) () `seq` ()
()
λ import Control.Monad.State.Strict
λ import Debug.Trace
λ runStateT ((trace "Hi" $ return ()) >> return undefined :: State () ()) () `seq` ()
Hi
()
λ import Control.Concurrent
λ forkIO $ return undefined
ThreadId 534
λ forkIO $ return $! undefined
<interactive>: Prelude.undefined
ThreadId 580
Some good links about laziness here, here, here, here, here and here.