我知道可以更改包装类型,以便您可以
f :: (a -> m b)
g :: (b -> m c)
f >>= g :: (a -> m c)
但可以更改m
吗?如果m
是MonadError
并且由Either ErrorA
和Either ErrorB
实现,我可以以某种方式链接它们吗?显然我不能直接链接它们,因为Left
的类型是什么?但是,我遇到的情况是,在任何一种情况下我最终都会调用show
,但我找不到比
case mightFail1 of
Left e -> show e
Right v -> either show doStuff mightFail2
无法正确使用在第一次错误时停止的monadic行为,而无需我明确检查。
答案 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