是否可以在monadic序列中更改monad类型?

时间:2013-08-05 19:27:53

标签: haskell monads either

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

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

但可以更改m吗?如果mMonadError并且由Either ErrorAEither ErrorB实现,我可以以某种方式链接它们吗?显然我不能直接链接它们,因为Left的类型是什么?但是,我遇到的情况是,在任何一种情况下我最终都会调用show,但我找不到比

更好的解决方案
case mightFail1 of
  Left e -> show e
  Right v -> either show doStuff mightFail2

无法正确使用在第一次错误时停止的monadic行为,而无需我明确检查。

4 个答案:

答案 0 :(得分: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

Bifunctors实现了大致相同的效果,但它们是查看问题的完全不同的方式。它们不是通常更换容器,而是影响容器本身的另一个协变参数(或索引)。因此,Bifunctor动作总是被视为自然变换,但NT更为通用。

答案 1 :(得分:7)

无法在monadic链中执行此操作。

请注意,这根本不关心Monad:你没有以任何方式绑定Left参数中的嵌套monadic动作或类似的东西,但你只是改变论证本身。它基本上是仿函数操作fmap,但是在左边而不是右边部分:

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

令人惊讶的是,具有该特定签名的函数doesn't seem to exist。然而,这个带有两个协变参数的仿函数的概念显然比Either更为一般,而且确实there's a dedicated class。它有(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

答案 2 :(得分:4)

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

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

既然你说你最终会show这两个人,你可以使用fmapL show统一他们两个以同意Either String monad并直接对它们进行排序:< / p>

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

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

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

example :: Either (Either Error1 Error2) ()
example = do
    v <- fmapL Left mightFail1
    fmapL Right $ mightFail2 v

答案 3 :(得分:2)

StackOverflow是一款出色的橡皮鸭。我找到了一个解决方案,但是如果有另一种方法,我仍然很好奇。由于我已经写完了这个问题,我还是会发布它。

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

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

这将整个调用包装到(f mightFail1),可能有一个可组合的变体(f . mightFail1

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