总结:不同的堆栈顺序产生不同的业务逻辑
也就是说,堆栈的不同monad转换器顺序不仅会影响评估顺序,还会影响程序的功能。
在演示订单的影响时,人们通常使用最简单的转换器,例如ReaderT、WriterT、StateT、MaybeT、ExceptT。它们的不同顺序不会给出显着不同的业务逻辑,因此很难清楚地理解其影响。此外,它们的一些子集是可交换的,即没有功能差异。
出于演示目的,我建议使用StateT 和ListT,它们揭示了monad 堆栈上transformer order 之间的巨大差异。
背景:StateT 和 ListT
-
StateT: State monad 在 For a Few Monads More 中有很好的解释。 StateT 只是给你更多的力量——使用它底层的m 的一元操作。如果您知道evalStateT、put、get 和modify 就足够了,这些在许多State monad 教程中都有说明。
-
ListT:List,又名[],是一个单子(在A Fistful of Monads 中解释)。 ListT m a(在包 list-t 中)为您提供类似于 [a] 的内容以及底层 monad m 的所有单子操作。棘手的部分是ListT 的执行(类似于evalStateT):有很多执行方式。想想你在使用 evalStateT、runStateT 和 execState 时关心的不同结果,List monad 的上下文有很多潜在的消费者,例如只是检查他们,即,@ 987654354@,折叠它们,即fold,等等。
实验:了解 Monad Transformer 顺序影响
我们将在IO 之上使用StateT 和ListT 构建一个简单的两层monad 转换器堆栈,以实现一些功能进行演示。
任务说明
汇总流中的数字
流将被抽象为Integers 的列表,因此我们的ListT 进来了。总结它们,我们需要在处理流中的每个项目时保持总和的状态,其中我们的@ 987654361@来了。
两堆
我们有一个简单的状态 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 无法实现在列表/流项操作之间保持状态的目的。