【发布时间】:2012-02-21 16:58:56
【问题描述】:
我有一个问题,一堆单子变压器(甚至一个单子变压器)超过IO。一切都很好,除了在每个动作之前使用提升非常烦人!我怀疑这真的没什么可做的,但我想我还是会问的。
我知道提升整个块,但如果代码真的是混合类型怎么办?如果 GHC 加入一些语法糖会不会很好(例如,<-$ = <- lift)?
【问题讨论】:
标签: haskell monads monad-transformers
我有一个问题,一堆单子变压器(甚至一个单子变压器)超过IO。一切都很好,除了在每个动作之前使用提升非常烦人!我怀疑这真的没什么可做的,但我想我还是会问的。
我知道提升整个块,但如果代码真的是混合类型怎么办?如果 GHC 加入一些语法糖会不会很好(例如,<-$ = <- lift)?
【问题讨论】:
标签: haskell monads monad-transformers
对于所有标准的mtl monad,您根本不需要lift。 get、put、ask、tell — 它们都可以在任何单子中工作,并且在堆栈中的某个位置使用正确的变压器。缺少的部分是 IO,即使在那里,liftIO 也会将任意 IO 操作提升到任意数量的层。
这是通过提供的每个“效果”的类型类完成的:例如,MonadState 提供 get 和 put。如果您想围绕转换器堆栈创建自己的newtype 包装器,您可以使用GeneralizedNewtypeDeriving 扩展来执行deriving (..., MonadState MyState, ...),或者滚动您自己的实例:
instance MonadState MyState MyMonad where
get = MyMonad get
put s = MyMonad (put s)
您可以使用它来选择性地公开或隐藏组合转换器的组件,方法是定义一些实例而不是其他实例。
(通过定义自己的类型类并为标准转换器提供样板实例,您可以轻松地将这种方法扩展到您自己定义的全新 monadic 效果,但全新的 monad 很少见;大多数时候,您将只需编写 mtl 提供的标准集即可。)
【讨论】:
您可以通过使用类型类而不是具体的 monad 堆栈来使您的函数与 monad 无关。
假设你有这个功能,例如:
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),也没有方便的类型类。那么当你想任意解除 IO 动作时应该怎么做呢?
救援来了MonadIO!它的行为与MonadState、MonadReader 等几乎相同,唯一的区别是它的提升机制略有不同。它的工作原理如下:您可以采取任何IO 操作,并使用liftIO 将其转换为与monad 无关的版本。所以:
action :: IO ()
liftIO action :: MonadIO m => m ()
通过以这种方式转换您希望使用的所有 monadic 动作,您可以随意地将 monad 交织在一起,而无需任何繁琐的提升。
【讨论】:
{-# LANGUAGE FlexibleContexts #-} pragma。