Haskell - 适用于左派

Haskell - Applicative upon Either's Left

我正在尝试了解 Applicative 和 Either's Left。这是来源:

instance Applicative (Either e) where
    pure          = Right
    Left  e <*> _ = Left e
    Right f <*> r = fmap f r

我无法理解 Left e <*> _ = Left e 部分。这没有意义,因为:

Left (+3) <*> Right 5

会returnLeft (+3),而这个:

Right (+1) <*> Left 3

会returnLeft 3。问题是不一致。他们为什么要这样做?如果我的问题不够干净,我深表歉意。谢谢!

TL;DR,这是一个有意的设计决定。

您应该将 Right 视为“默认”状态,将 Left 视为“后备”状态。我确实想对你上面的陈述做一个小的更正。 Left (+3) <*> Right 5 不会像你说的那样产生 (+3),而是产生 Left (+3)。这是一个重要的区别。第二个更正是 Right (+1) <*> Left 3 不是 Left 4,而是 Left 3。同样,这对于了解正在发生的事情很重要。

<*> 运算符不能在 Either 上对称的原因是因为 LeftRight 构造函数不采用相同的类型。让我们看一下专门用于 Either 仿函数的 <*> 的类型:

(<*>) :: Either a (b -> c) -> Either a b -> Either a c

请注意第一个参数的 Right 端是一个函数。这样您就可以使用 (<*>) 将参数链接在一起,如下所示:

Right (+) <$> Right 3 <*> Right 2
> Right 5

但是如果第一个参数是 Left 3:

Right (+) <$> Left 3 <*> Right 2
> (Right (+) <$> Left 3) <*> Right 2
> Left 3 <*> Right 2
> Left 3

这也意味着当LeftRight的类型不同时,一般可以使用(<*>)。如果 Left (+3) <*> Right 5 应该产生 Left 8,那么 Left (++ "world") <*> Right 5 应该产生什么,因为它们都可以被强制转换为相同的类型,即 Num a => Either (String -> String) a?当 LeftRight 不是同一类型时,不可能得出一个令人满意的答案,并且 Either 的版本仅限于携带一种类型严重阻碍效用。

这还允许您以某种方式将 Left 值视为异常值。如果在任何阶段,您最终得到一个 Left 值,Haskell 将停止执行计算并直接将 Left 值一直向上级联。这也恰好与许多人思考编程的方式相吻合。您可以想象为 LeftRight 值创建备用计算集,但在许多情况下,您最终只会用 id 填充 Left 计算,所以这在实践中并不是太大的限制。如果你想执行一对分支计算中的一个,你应该使用常规分支语法,例如守卫、模式或 caseif 语句,然后将值包装在 Either 最后。

考虑实例的这个等效定义:

instance Applicative (Either e) where
    pure = Right
    lhs <*> rhs = case lhs of
                      Right f -> fmap f rhs
                      otherwise -> lhs

如果 lhs 不是 Right,它一定是 Left,所以我们 return 它 as-is。我们实际上根本不需要匹配包装值。如果它 一个 Right,我们将推迟到 Functor 实例以找出得到 returned.

的内容
instance Functor (Either a) where
    fmap f (Right x) = Right (f x)
    fmap _ l = l

同样,我给出了一个定义,强调 Left 值的 content 无关紧要。如果第二个参数不是 Right,我们不必显式匹配它;它必须是 Left,我们可以 return 它 as-is.

如果你想知道Right … <*> Left …怎么还能return一个Left,那是因为这个定义中的fmap调用:

instance Applicative (Either e) where
    pure          = Right
    Left  e <*> _ = Left e
    Right f <*> r = fmap f r

如果我们将 fmap 的定义扩展为 Either,那么 <*> 的定义如下所示:

Left  e <*> _ = Left e
Right f <*> r = case r of
  Left e -> Left e
  Right x -> Right (f x)

或者,更对称地写出所有情况,明确拼写:

Left  e1 <*> Left  _e2 = Left e1      -- 1
Left  e  <*> Right _x  = Left e       -- 2
Right _f <*> Left  e   = Left e       -- 3
Right f  <*> Right x   = Right (f x)  -- 4

我用下划线标记了 _ 被丢弃的值。

请注意 情况下 returns Right 是两个输入均为 Right 的情况。事实上,这是唯一一次 可能 到 return Right

情况(4)我们只有一个Right (f :: a -> b)和一个Right (x :: a);我们没有 e,所以我们不能 return Left,我们必须获得 b 的唯一方法是应用 fx.

在情况(1)、(2)、(3)中,我们必须return一个Left,因为至少有一个输入是Left,所以我们缺少我们需要生成 b.

a -> ba

两个输入都是Left时(1),Either偏向第一个参数。

有一种类似于 Either 的类型称为 Validation,它 结合了 它的“失败”案例,而不是选择一个或另一个,但它是更受限制:它只是一个 Applicative,而 Either 既是 Applicative 又是 Monad.