用Monad变形金刚避免电梯
我有一个问题,非常适合使用堆栈的MT(甚至一个MT)通过IO。 一切都很好,只是在每个动作之前使用电梯是非常烦人的! 我怀疑这件事真的没有什么可做,但我想我会问。
我知道提升整个块,但如果代码真的是混合types呢? 如果GHC放入一些句法糖(例如<-$
= <- lift
),会不会很好?
对于所有标准的mtl monad,你根本不需要lift
。 get
, put
, ask
, tell
– 他们都在任何一个monad工作在正确的变压器堆栈中的某个地方。 缺less的部分是IO
,即使在那里, liftIO
也liftIO
在任意数量的层上提升任意的IO操作。
这是通过为每个“效果”提供的types类来完成的:例如, MonadState
提供了get
和put
。 如果你想在转换器堆栈中创build你自己的newtype
包装器,可以使用GeneralizedNewtypeDeriving
扩展来deriving (..., MonadState MyState, ...)
,或者转换你自己的实例:
instance MonadState MyState MyMonad where get = MyMonad get put s = MyMonad (put s)
你可以通过定义一些实例而不是其他的方法来select性地显示或隐藏组合变换器的组件。
(您可以轻松地将这种方法扩展到您自己定义的全新monadic效果,方法是定义您自己的types类别并为标准变形金刚提供样板实例,但是全新的monad很less见;大多数情况下,组成由mtl提供的标准集合)。
你可以通过使用typeclasses而不是具体的monad栈来使你的函数monad-agnostic。
比方说,你有这个function,例如:
bangMe :: State String () bangMe = do str <- get put $ str ++ "!" -- or just modify (++"!")
当然,你也意识到它也是一个变压器,所以你可以这样写:
bangMe :: Monad m => StateT String m ()
但是,如果你有一个使用不同堆栈的函数,比如说ReaderT [String] (StateT String IO) ()
或者其他东西,那么你将不得不使用可怕的lift
函数! 那么如何避免?
诀窍是使函数签名更通用,因此它说State
monad可以出现在monad堆栈中的任何地方。 这是这样做的:
bangMe :: MonadState String m => m ()
这迫使m
成为monad堆栈中支持状态(虚拟)的monad,因此该函数不会为任何此类堆栈提升。
有一个问题,虽然; 因为IO
不是mtl
一部分,所以默认情况下它没有变换器(例如IOT
)和方便的types类。 那么当你想任意提升IO操作时你应该怎么做?
MonadIO
来MonadIO
! 它的行为几乎与MonadState
, MonadReader
等一样,唯一的区别是它有一个稍微不同的提升机制。 它的工作原理是这样的:您可以采取任何IO
操作,并使用liftIO
将其变成monad不可知的版本。 所以:
action :: IO () liftIO action :: MonadIO m => m ()
通过改变所有你想使用的monadic动作,你可以随心所欲地交织monad,而不需要繁琐的提升。