问题描述
我正在尝试理解Applicative和任一左派。来源:
80
我无法理解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
,同时:
Left (+3)
将返回Right (+1) <*> Left 3
。问题是不一致。为什么要这样做?如果我的问题不够清楚,我深表歉意。谢谢!
解决方法
TL; DR,这是一个故意的设计决定。
您应该将Right
视为“默认”状态,并将Left
视为“后备”状态。我确实想对您的上述声明做一些小的更正。 Left (+3) <*> Right 5
不会像您所说的那样产生(+3)
,而是产生Left (+3)
。这是一个重要的区别。第二个更正是Right (+1) <*> Left 3
不是Left 4
,而是Left 3
。同样,这对于了解正在发生的事情很重要。
<*>
运算符不能在Either
上对称的原因是,Left
和Right
构造函数的类型不同。让我们看看专门针对<*>
仿函数的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
这也意味着,当(<*>)
和Left
的类型不同时,通常可以使用Right
。如果Left (+3) <*> Right 5
应该产生Left 8
,那么Left (++ "world") <*> Right 5
应该产生什么,假设它们都可以被强制转换为同一类型,即Num a => Either (String -> String) a
?当Left
和Right
不是同一类型时,不可能给出令人满意的答案,因为它们Either
被限制为只能携带一种类型严重阻碍了实用程序。
这还允许您以某种方式将Left
值视为例外。如果在任何阶段最终得到一个Left
值,Haskell将停止执行计算,而只是将Left
值一直向上级联。这也恰好与许多人对编程的思考方式相匹配。您可以想象为Left
和Right
值创建替代的计算集,但是在许多情况下,最终还是要用Left
来填充id
计算,因此这在实践中并没有太大的限制。如果要执行一对分支计算中的一个,则应使用常规分支语法(例如警卫队,模式或case
和if
语句),然后将值包装在{{1}中}。
考虑实例的以下等效定义:
instance Applicative (Either e) where
pure = Right
lhs <*> rhs = case lhs of
Right f -> fmap f rhs
otherwise -> lhs
如果lhs
不是Right
,则它必须是Left
,因此我们将其原样返回。实际上,我们根本不需要与包装的值匹配。如果是是Right
,我们将遵循Functor
实例来查找返回的内容。
instance Functor (Either a) where
fmap f (Right x) = Right (f x)
fmap _ l = l
我再次给出一个定义,强调Left
值的 content 无关紧要。如果第二个参数不是Right
,则不必在其上显式匹配。它必须是Left
,我们可以原样返回它。
如果您想知道Right … <*> Left …
仍如何返回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
我用下划线_
标记了所丢弃的值。
请注意,当两个输入均为Right
时,返回Right
的 only 情况。实际上,这是可能唯一返回Right
的时间。
在情况(4)中,我们只有一个Right (f :: a -> b)
和一个Right (x :: a)
;我们没有e
,因此我们无法返回Left
,而获得b
的唯一方法是将f
应用于{{ 1}}。
在情况(1),(2)和(3)中,我们必须返回一个x
,因为至少有一个输入是Left
,所以我们缺少了{{1 }}或我们需要产生Left
的{{1}}。
在情况(1)中,当两个输入均为a -> b
时,a
偏向第一个参数。
有一种类似于b
的类型称为Left
,它组合其“失败”情况,而不是选择其中一种,但受其限制:仅Either
,而Either
既是Validation
又是Applicative
。