7

目前我在使用 monad 转换器时遇到了一些困难。我正在定义一些使用变压器的不同的非确定性关系。不幸的是,我无法理解如何将一种有效的模型清晰地转换为另一种。

假设这些关系是“foo”和“bar”。假设“foo”将 As 和 Bs 与 Cs 联系起来;假设“bar”将 Bs 和 Cs 与 Ds 联系起来。我们将根据“foo”来定义“bar”。更有趣的是,这些关系的计算将以不同的方式失败。(因为 bar 关系依赖于 foo 关系,所以它的失败案例是一个超集。)因此我给出了以下类型定义:

data FooFailure = FooFailure String
data BarFailure = BarSpecificFailure | BarFooFailure FooFailure
type FooM = ListT (EitherT FooFailure (Reader Context))
type BarM = ListT (EitherT BarFailure (Reader Context))

然后,我希望能够使用以下函数签名编写关系:

foo :: A -> B -> FooM C
bar :: B -> C -> BarM D

我的问题是,在编写“bar”的定义时,我需要能够从“foo”关系接收错误并在“bar”空间中正确表示它们。所以我会很好的形式的功能

convert :: (e -> e') -> ListT (EitherT e (Reader Context) a
                     -> ListT (EitherT e' (Reader Context) a

我什至可以通过运行 ListT,映射到 EitherT,然后重新组装 ListT 来编写那个小野兽(因为碰巧 m [a] 可以转换为 ListT ma)。但这似乎……很乱。

我不能只运行一个变压器,在它下面做一些事情,然后通常“放回去”,这是有充分理由的;我运行的变压器可能会产生影响,我无法神奇地撤消它们。但是有什么方法可以让我将一个函数提升到一个变压器堆栈中足够远的地方来为我做一些工作,这样我就不必编写convert上面显示的函数了?

4

1 回答 1

3

我认为 convert 是一个很好的答案,并且使用Control.Monad.MorphControl.Monad.Trans.Either(几乎)编写起来非常简单:

convert :: (Monad m, Functor m, MFunctor t)
           => (e -> e')
           -> t (EitherT e m) b -> t (EitherT e' m) b
convert f = hoist (bimapEitherT f id)

小问题是这ListT不是MFunctor. 我认为这是作者抵制ListT,因为它不遵循 monad 转换器法则,因为它很容易编写类型检查实例

instance MFunctor ListT where hoist nat (ListT mas) = ListT (nat mas)

无论如何,通常看一下Control.Monad.Morph处理变压器堆栈(部分)的自然变换。我会说这符合将函数“足够”提升到堆栈中的定义。

于 2013-05-14T23:52:58.810 回答