我正在寻找讨论组成monad的良好实践的资源。我最紧迫的问题是我正在编写一个系统,该系统正在使用一系列状态monad而不是不同的状态类型,似乎处理这种情况的最好方法就是创建一个大的产品类型(可能是prettied)即使第1阶段对组件B不感兴趣,第2阶段仅对组件A.1感兴趣,也包括我感兴趣的所有组件。
在这类领域编写代码时,我非常感谢能够很好地讨论替代方案。我自己的代码库是在Scala中,但我很高兴阅读有关Haskell中相同问题的讨论。
答案 0 :(得分:8)
使用StateT
的堆栈有点困难,因为当您撰写get
或put
时,您正在谈论哪一层会让您感到困惑。如果你使用transformers
样式的显式堆栈,则必须使用一堆lift
s,如果使用mtl
的基于类的方法,则完全卡住了。
-- using transformers explicit stack style
type Z a = StateT Int (StateT String IO) a
go :: Z ()
go = do int <- get
str <- lift get
replicateM int (liftIO $ putStrLn str)
我们可能希望避免使用明确的产品类型的状态。由于我们最终得到了从产品状态到每个单独组件的功能,因此使用get
gets
进入这些单个组件
data ZState = ZState { int :: Int, str :: String }
type Z a = StateT ZState IO a
go :: Z ()
go = do i <- gets int
s <- gets str
replicateM i (liftIO $ putStrLn s)
但这可能会被认为是丑陋的,原因有两个:(1)put
一般情况下修改并没有任何好的故事和(2)我们不容易看到在仅影响的函数类型,例如int
状态,并知道它不会触及str
。我们更喜欢保持这种类型保证的模块化。
如果您lens
- 精明,那就是一个名为zoom
-- the real type is MUCH more general
zoom :: Lens' mother child -> StateT child m a -> StateT mother m a
&#34;提升&#34;对一个较大的状态空间的子部分进行有状态计算直到整个状态空间。或者,实际上,我们这样使用它:
data ZState = ZState { _int :: Int, _str :: String }
makeLenses ''ZState
type Z = StateT ZState IO a
inc :: MonadState Int m => m ()
inc = modify (+1)
yell :: MonadState String m => m ()
yell = modify (map toUpper)
go :: Z ()
go = do zoom int $ do inc
inc
inc
zoom str yell
i <- use int
s <- use str
replicateM i (liftIO $ putStrLn s)
现在大多数问题都应该消失了 - 我们可以zoom
来隔离只依赖于inc
和yell
等总状态子集的有状态操作并确定它们隔离在他们的类型。我们还可以get
使用use
zoom
内部状态组件。
除此之外,zoom
可用于type Z a = EitherT String (ListT (StateT ZState IO)) a
>>> :t zoom int :: EitherT String (ListT (StateT Int IO)) a -> Z a
zoom int :: EitherT String (ListT (StateT Int IO)) a -> Z a
处于深埋在各种变压器堆栈内的状态。完全通用的类型在这种情况下工作得很好
zoom
虽然这非常好,但完全通用{{1}}需要一些重要的诡计,你只能缩放一些变形金刚层。 (今天我很不清楚你如何将这个功能添加到你自己的图层,尽管可能是这样的。)