I am attempting to define an API to express a particular type of procedure in my program.
newtype Procedure a = { runProcedure :: ? }
There is
That is to say, different monad transformer orders of the stack do not only affect the evaluation orders, but also the functionalities of programs.
When demonstrating the impact of orders, people usually use the simplest transformers such as ReaderT, WriterT, StateT, MaybeT, ExceptT. Different orders of them do not give dramatically different business logic, so it is hard to understand the impact clearly. In addition, some subsets of them are commutative, i.e., there is no functionality differences.
For demonstration purpose, I suggest to use StateT and ListT, which reveal the dramatic difference between transformer orders on monad stacks.
StateT and ListTStateT: State monad is well explained in For a Few Monads More. StateT just gives you a little bit more power -- using the monadic operations of its underlying m. It is sufficient if you know evalStateT, put, get, and modify, which are explained in many State monad tutorials.ListT: List, a.k.a, [], is a monad (explained in A Fistful of Monads). ListT m a (in package list-t) gives you something similar to [a] plus all monadic operations of the underlying monad m. The tricky part is the execution of ListT (something comparable to evalStateT): there are lots of ways of execution. Think about different outcomes you care when using evalStateT, runStateT, and execState, the context of List monad has lots of potential consumers such as just go over them, i.e., traverse_, fold them, i.e., fold, and more.We will construct a simple two-layer monad tranformers stack using StateT and ListT on top of IO to fulfill some functionalities for demonstration.
Summing up numbers in a stream
The stream will be abstracted as a list of Integers, so our ListT comes in. To sum them up, we need to keep a state of the sum while processing each item in the stream, where our StateT comes.
We have a simple state as Int to keep the sum
ListT (StateT Int IO) aStateT Int (ListT IO) a#!/usr/bin/env stack
-- stack script --resolver lts-11.14 --package list-t --package transformers
import ListT (ListT, traverse_, fromFoldable)
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.State (StateT, evalStateT, get, modify)
main :: IO()
main = putStrLn "#### Task: summing up numbers in a stream"
>> putStrLn "#### stateful (StateT) stream (ListT) processing"
>> putStrLn "#### StateT at the base: expected result"
>> ltst
>> putStrLn "#### ListT at the base: broken states"
>> stlt
-- (ListT (StateT IO)) stack
ltst :: IO ()
ltst = evalStateT (traverse_ (\_ -> return ()) ltstOps) 10
ltstOps :: ListT (StateT Int IO) ()
ltstOps = genLTST >>= processLTST >>= printLTST
genLTST :: ListT (StateT Int IO) Int
genLTST = fromFoldable [6,7,8]
processLTST :: Int -> ListT (StateT Int IO) Int
processLTST x = do
liftIO $ putStrLn "process iteration LTST"
lift $ modify (+x)
lift get
printLTST :: Int -> ListT (StateT Int IO) ()
printLTST = liftIO . print
-- (StateT (ListT IO)) stack
stlt :: IO ()
stlt = traverse_ (\_ -> return ())
$ evalStateT (genSTLT >>= processSTLT >>= printSTLT) 10
genSTLT :: StateT Int (ListT IO) Int
genSTLT = lift $ fromFoldable [6,7,8]
processSTLT :: Int -> StateT Int (ListT IO) Int
processSTLT x = do
liftIO $ putStrLn "process iteration STLT"
modify (+x)
get
printSTLT :: Int -> StateT Int (ListT IO) ()
printSTLT = liftIO . print
$ ./order.hs
#### Task: summing up numbers in a stream
#### stateful (StateT) stream (ListT) processing
#### StateT at the base: expected result
process iteration LTST
16
process iteration LTST
23
process iteration LTST
31
#### ListT at the base: broken states
process iteration STLT
16
process iteration STLT
17
process iteration STLT
18
The first stack ListT (StateT Int IO) a yields the correct result since StateT is evaluated after ListT. When evaluating StateT, the runtime system already evaluated all operations of ListT -- feeding the stack with a stream [6,7,8], going through them with traverse_. The word evaluated here means effects of ListT are gone and ListT is transparent to StateT now.
The second stack StateT Int (ListT IO) a does not have the correct result since StateT is too short-lived. In every iteration of ListT evaluation, a.k.a., traverse_, the state is created, evaluated and vanished. The StateT in this stack structure does not achieve its purpose to keep states between list/stream item operations.