15

我发现 monad 转换器的问题之一是需要对lift正确的 monad 进行操作。一个lift在这里和那里都不错,但有时有些功能看起来像这样:

fun = do
  lift a
  lift b
  c
  lift d
  lift e
  f

我希望能够编写这个函数:

fun = monadInvert $ do
  a
  b
  lift c
  d
  e
  lift f

这将 s 的数量减半lift,并使代码更清晰。

问题是:什么单子是monadInvert可能的?应该如何创建这个功能?

加分点:定义它monad mMonadIO.

这个问题的标题谈到了排列:确实,我们如何处理 monad 转换器堆栈的任意排列?

4

3 回答 3

16

嗯,首先,你实际上并不需要这么多的提升。对于 monad 转换器,以下恒等式成立:

lift c >>= lift . f = lift (c >>= f)
lift c1 >> lift c2  = lift (c1 >> c2)

写法并不少见:

x <- lift $ do
    {- ... -}

接下来是:当您使用mtlmonadLib 之类的库(即基于类型类的库而不是直接使用转换器)时,您实际上可以直接访问大多数底层 monad:

c :: StateT MyState (ReaderT MyConfig SomeOtherMonad) Result
c = do
    x <- ask
    y <- get
    {- ... -}

最后,如果尽管有这两点,您确实需要大量提升,您应该考虑编写自定义 monad 甚至使用完全不同的抽象。我发现自己使用自动机箭头进行有状态计算而不是状态单子。

于 2011-12-05T10:28:31.513 回答
10

您可能对Tom Schrijvers 和 Bruno Oliveira的Monads、Zippers 和 Views、Virtualizing the Monad Stack感兴趣。

这并没有解决您关于减少提升的观点,但它是解决您的“单子排列”问题的一种有趣方法。

这是摘要:

这项工作旨在通过采用两种新技术来虚拟化 monad 堆栈:monad zipper 和 monad 视图,使 monadic 组件更具可重用性和对更改的鲁棒性。monad zipper 是一个 monad 转换器,它通过忽略具体堆栈中的特定层来创建虚拟 monad 堆栈。Monad 视图为 monad 堆栈虚拟化提供了一个通用框架:它们使 monad 拉链更进一步,并将其与广泛的其他虚拟化集成。例如,特定视图允许对堆栈中的 monad 进行受限访问。此外,组件可以使用 monad 视图来提供类似引用调用的机制来访问 monad 堆栈的特定层。

于 2011-12-05T10:19:36.150 回答
1

我很确定你所描述的对于 IO 来说是不可能的,它总是最里面的单子:

来自 Martin Grabmüller:Monad Transformers Step by Step,可在 http://www.grabmueller.de/martin/www/pub/

在本文档中,我们在 eval6 中调用 liftIO 来执行 I/O 操作。在这种情况下,为什么我们需要解除?因为没有可以将类型实例化为的 IO 类。因此,对于 I/O 动作,我们必须调用 lift 向内发送命令

通常,对于限制比 IO 少的 monad,(例如 Error 和 State)顺序对语义仍然很重要,因此您不能仅仅为了使语法更方便而更改堆栈的顺序。

对于一些像 Reader 这样的 monad,它们是中心的(因为它们确实在堆栈中通勤),你的想法似乎并非不可能。虽然我不知道怎么写。我想这将是一个类型类CentralMonad,它ReaderT是一个实例,具有一些实现......

于 2012-01-17T22:34:35.107 回答