6

我知道可以更改包装类型,以便您可以拥有

f :: (a -> m b)
g :: (b -> m c)
f >>= g :: (a -> m c)

但有可能改变m吗?如果mis a MonadErrorand 由 an Either ErrorAand实现Either ErrorB,我可以以某种方式链接它们吗?显然我不能直接将它们链接起来,因为 的类型是Left什么?但是,无论哪种情况,我最终都会打电话show,但我没有找到比这更好的解决方案

case mightFail1 of
  Left e -> show e
  Right v -> either show doStuff mightFail2

它无法正确使用在第一个错误处停止的单子行为,而无需我进行明确检查。

4

4 回答 4

11

“改变容器”的整个概念被称为“自然转换”。具体来说,我们想要一个在不影响容器内部的情况下转换容器的函数。我们可以通过使用来确保类型系统中的情况forall

-- natural transformation
type (m :~> n) = forall a. m a -> n a

然后可以随时应用这些。例如,如果您可以转换,那么您可以ErrorA -> ErrorB进行一般操作

mapE :: (e -> e') -> (Either e :~> Either e')
mapE f (Left e)  = Left (f e)
mapE _ (Right a) = a

您甚至可以对类型运算符和求和类型非常感兴趣。

-- a generic sum type
infixr 9 :+:
newtype (a :+: b) = Inl a | Inr b

liftE :: (Either e :~> Either (e' :+: e))
liftE = mapE Inr

双函子实现了大致相同的效果,但它们是看待问题的完全不同的方式。它们通常不会改变容器,而是影响容器本身的另一个协变参数(或索引)。因此,Bifunctor 动作总是可以看作是自然变换,但 NT 更一般。

于 2013-08-05T22:21:16.150 回答
7

在单子链中不可能做到这一点。

请注意,这根本不是真正关心的问题Monad:您不会以任何方式在Left参数中绑定嵌套的单子动作或类似的东西,但您只是在转换参数本身。它基本上是函子操作fmap,但在左侧而不是右侧部分:

fmap     :: (r->ρ) -> Either l r -> Either l ρ
fmapLeft :: (l->λ) -> Either l r -> Either λ r

令人惊讶的是,具有该特定签名的函数似乎并不存在。然而,这个带有两个协变参数的函子的想法显然比 更普遍Either,而且确实有一个专门的类。它有(IMO 相当不幸的命名,与 冲突Arrow

Data.Bifunctor.first :: (a -> b) -> p a c -> p b c

这实际上专门用于

first :: (a -> b) -> Either a c -> Either b c

所以你可以使用

f :: (Show a) => (Either a b) -> (Either String b)
f = first show
于 2013-08-05T20:33:57.890 回答
4

对于这种特定情况,您可以fmapL从我的errors库中使用:

fmapL :: (a -> b) -> Either a r -> Either b r

既然你说你最终会去show他们两个,你可以使用fmapL show统一他们两个来就Either Stringmonad 达成一致并直接对它们进行排序:

do v <- fmapL show mightFail1
   fmapL show $ mightFail2 v

现在您可以使用 do 表示法对它们进行排序,并让它们共享相同的错误处理机制。

请注意,这show不是统一左值的唯一方法。您还可以使用...统一不可显示的值Either

example :: Either (Either Error1 Error2) ()
example = do
    v <- fmapL Left mightFail1
    fmapL Right $ mightFail2 v
于 2013-08-06T00:44:53.817 回答
2

StackOverflow 是一个优秀的橡皮鸭。我找到解决方案,但我仍然很好奇是否有另一种方法。既然我已经写完问题,我还是会发布它。

不要考虑“在链中更改 monad 类型”,只需在链接之前转换所有值,方法是让它们返回Either String a

f :: (Show a) => (Either a b) -> (Either String b)
f = either (Left . show) (Right)

这包含了对任何一个 ( ) 的整个调用f mightFail1,可能有一个可组合的变体 ( f . mightFail1)

疯狂模式:将任何一个包装成一个新类型,使其成为将函数映射到左侧而不是右侧的函子实例,然后调用 fmap show mightFail1 (不要忘记包装和解开你的新类型)。那有意义吗?:D

于 2013-08-05T19:36:38.387 回答