使用monad变压器避免升力

ael*_*ndy 49 monads haskell monad-transformers

我有一个问题,一堆monad变压器(甚至一个monad变压器)结束IO.一切都很好,除了在每次动作之前都使用电梯非常烦人!我怀疑这与此无关,但我想我还是会问.

我知道提升整个块,但如果代码实际上是混合类型怎么办?如果GHC投入一些语法糖(例如,<-$= <- lift),这不是很好吗?

ehi*_*ird 56

对于所有标准的mtl monad,你根本不需要lift.get,put,ask,tell-他们都在用正确的变压器某处栈中的任何单子工作.丢失的部分是IO,甚至在liftIO任意数量的层中提升任意IO动作.

这是通过提供每个"效果"的类型类来完成的:例如,MonadState提供getput.如果要newtype围绕变换器堆栈创建自己的包装器,可以deriving (..., MonadState MyState, ...)使用GeneralizedNewtypeDeriving扩展,或者滚动自己的实例:

instance MonadState MyState MyMonad where
  get = MyMonad get
  put s = MyMonad (put s)
Run Code Online (Sandbox Code Playgroud)

您可以通过定义某些实例而不是其他实例来选择性地公开或隐藏组合变换器的组件.

(您可以通过定义自己的类型类并为标准变换器提供样板实例,轻松地将此方法扩展到您自己定义的全新monadic效果,但是全新的monad很少见;大多数情况下,您只需简单地完成组成mtl提供的标准集.)


dfl*_*str 49

您可以通过使用类型类而不是具体的monad堆栈来使函数与monad无关.

假设你有这个功能,例如:

bangMe :: State String ()
bangMe = do
  str <- get
  put $ str ++ "!"
  -- or just modify (++"!")
Run Code Online (Sandbox Code Playgroud)

当然,你意识到它也可以用作变换器,所以可以写:

bangMe :: Monad m => StateT String m ()
Run Code Online (Sandbox Code Playgroud)

但是,如果你有一个使用不同堆栈的函数,ReaderT [String] (StateT String IO) ()或者说,或者其他什么,你将不得不使用可怕的lift函数!那怎么回避?

诀窍是使函数签名更通用,因此它表示Statemonad可以出现在monad堆栈中的任何位置.这样做是这样的:

bangMe :: MonadState String m => m ()
Run Code Online (Sandbox Code Playgroud)

这迫使m它成为monad,它支持monad堆栈中任何地方的状态(虚拟),因此该函数可以在不提升任何此类堆栈的情况下工作.

但是有一个问题; 因为IO它不属于mtl它,所以它没有变换器(例如IOT),也没有默认的方便类型类.那么当你想要任意提升IO动作时,你应该怎么做?

救援来了MonadIO!它的行为几乎完全相同MonadState,MonadReader唯一的区别在于它具有略微不同的提升机制.它的工作方式如下:您可以执行任何IO操作,并将liftIO其转换为monad不可知版本.所以:

action :: IO ()
liftIO action :: MonadIO m => m ()
Run Code Online (Sandbox Code Playgroud)

通过改变你希望以这种方式使用的所有monadic动作,你可以随心所欲地交织monad而不需要任何繁琐的提升.

  • 我和ehird为这个问题提供了一些不同的解决方案.可能值得阅读两个回答,以了解你有的替代品:) (5认同)