变形金刚之下的变革
目前,我对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
函数?
我认为转换是一个很好的答案,并使用Control.Monad.Morph
和Control.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
的作者,因为它不遵循单子变换法则,因为很容易编写一个类型检查实例
instance MFunctor ListT where hoist nat (ListT mas) = ListT (nat mas)
无论如何,一般来看看Control.Monad.Morph
来处理(部分)变压器堆栈上的自然变换。 我认为这符合将函数“足够”提升到堆栈的定义。