总结:不同的堆栈顺序产生不同的业务逻辑
也就是说,堆栈的不同monad转换器顺序不仅会影响评估顺序,还会影响程序的功能。
在演示订单的影响时,人们通常使用最简单的变压器,例如ReaderT
, WriterT
, StateT
, MaybeT
, ExceptT
。它们的不同顺序不会给出显着不同的业务逻辑,因此很难清楚地理解其影响。此外,它们的一些子集是可交换的,即没有功能差异。
出于演示目的,我建议使用StateT
and ListT
,它揭示了 monad 堆栈上的转换器顺序之间的巨大差异。
背景:StateT
和ListT
StateT
: State
monad 在For a Few Monads More中有很好的解释。StateT
只是给你更多的权力——使用它底层的一元操作m
。如果您知道evalStateT
, put
,get
和modify
, 就足够了,这些在许多State
monad 教程中都有解释。
ListT
: List
, aka, []
, 是一个 monad(在A Fistful of Monads中有解释)。ListT m a
(在 package 中list-t
)为您提供类似于[a]
plus 底层 monad 的所有 monadic 操作的东西m
。棘手的部分是执行ListT
(类似于evalStateT
):有很多执行方式。evalStateT
想想你在使用,runStateT
和时关心的不同结果,monadexecState
的上下文List
有很多潜在的消费者,例如只是遍历它们,即traverse_
,折叠它们,即,fold
等等。
实验:了解 Monad 变压器阶数影响
我们将构建一个简单的两层 monad 转换器堆栈,使用StateT
和ListT
在其之IO
上来实现一些功能以进行演示。
任务描述
汇总流中的数字
流将被抽象为Integer
s 的列表,所以我们ListT
进来了。总结它们,我们需要在处理流中的每个项目时保持总和的状态,我们StateT
来了。
两堆
我们有一个简单的状态Int
来保持总和
ListT (StateT Int IO) a
StateT 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
第一个堆栈ListT (StateT Int IO) a
产生正确的结果,因为StateT
在 之后进行评估ListT
。在评估StateT
时,运行时系统已经评估了所有操作ListT
- 用流向堆栈提供[6,7,8]
,用traverse_
. 这里评估的词意味着效果ListT
已经消失并且ListT
到现在是透明的StateT
。
第二个堆栈StateT Int (ListT IO) a
没有正确的结果,因为StateT
它的寿命太短了。在ListT
评估的每次迭代中,也就是,traverse_
状态被创建、评估和消失。在StateT
这个堆栈结构中并没有达到其在列表/流项目操作之间保持状态的目的。