用Monad变形金刚避免电梯

我有一个问题,非常适合使用堆栈的MT(甚至一个MT)通过IO。 一切都很好,只是在每个动作之前使用电梯是非常烦人的! 我怀疑这件事真的没有什么可做,但我想我会问。

我知道提升整个块,但如果代码真的是混合types呢? 如果GHC放入一些句法糖(例如<-$ = <- lift ),会不会很好?

对于所有标准的mtl monad,你根本不需要liftgetputasktell – 他们都在任何一个monad工作在正确的变压器堆栈中的某个地方。 缺less的部分是IO ,即使在那里, liftIOliftIO在任意数量的层上提升任意的IO操作。

这是通过为每个“效果”提供的types类来完成的:例如, MonadState提供了getput 。 如果你想在转换器堆栈中创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操作时你应该怎么做?

MonadIOMonadIO ! 它的行为几乎与MonadStateMonadReader等一样,唯一的区别是它有一个稍微不同的提升机制。 它的工作原理是这样的:您可以采取任何IO操作,并使用liftIO将其变成monad不可知的版本。 所以:

 action :: IO () liftIO action :: MonadIO m => m () 

通过改变所有你想使用的monadic动作,你可以随心所欲地交织monad,而不需要繁琐的提升。