Haskell 的 < | > 操作符是做什么的?

浏览 Haskell 的文档对我来说总是有点痛苦,因为所有关于函数的信息通常都只是: f a -> f [a],它可能意味着很多东西。

<|>函数的情况一样。

我得到的只有这个: (<|>) :: f a -> f a -> f a和它是 「联系二元运算」..。

通过对 Control.Applicative的检查,我了解到它根据实现做一些看似无关的事情。

instance Alternative Maybe where
empty = Nothing
Nothing <|> r = r
l       <|> _ = l

好的,如果没有左边,它就返回右边,否则它就返回左边,明白了。.这使我相信它是一个“左或右”操作符,考虑到它使用 ||作为“ OR”的历史用法,这有点意义

instance Alternative [] where
empty = []
(<|>) = (++)

除了这里,它只调用名单的连接接线员... 打破我的想法..。

那么这个函数到底是什么? 它的用途是什么? 它在事物的大格局中处于什么位置?

18185 次浏览

通常它的意思是“选择”或“并行”,因为 a <|> babab并行完成的“选择”。我们倒回去。

实际上,像 (<*>)(<|>)这样的类型类中的操作没有任何实际意义。这些操作通过两种方式被赋予意义: (1)通过法律和(2)通过实例化。如果我们不是在谈论 Alternative特别实例,那么只有(1)可用于直观的意义。

所以“联想”意味着 a <|> (b <|> c)(a <|> b) <|> c是一样的。这很有用,因为这意味着我们只关心与 (<|>)链接在一起的事物的 序列,而不是它们的“树结构”。

其他法律包括与 empty的同一性。特别是 a <|> empty = empty <|> a = a。在我们对“选择”或“平行”的直觉中,这些定律被理解为“ a 或(某些不可能的东西)必须是 a”或“ a 并行(空过程)只是 a”。这表明 emptyAlternative的某种“失效模式”。

关于 (<|>)/empty如何与 fmap(来自 Functor)或 pure/(<*>)(来自 Applicative)相互作用还有其他规律,但是也许在理解 (<|>)的意义方面向前推进的最佳方式是检查一个实例化 Alternative的类型的非常常见的例子: Parser

如果 x :: Parser Ay :: Parser B,那么 (,) <$> x <*> y :: Parser (A, B)按顺序解析 x 然后 y。相反,(fmap Left x) <|> (fmap Right y)解析 y :: Parser B0xy,从 x开始,尝试两种可能的解析。换句话说,它指示解析树中的 y :: Parser B1、选项或并行解析空间。

Alternative不是解析器或类似 MonadPlus 的东西的一个有趣的例子是 Concurrently,它是 async包中非常有用的类型。

对于 Concurrentlyempty是一个永远进行的计算。(<|>)并发执行它的参数,返回完成的第一个参数的结果,并取消另一个参数。

实际上,即使没有考虑 Alternative的定律,(<|>) :: f a -> f a -> f a也能告诉你很多东西。

它接受两个 f a值,并且必须返回一个。因此它必须以某种方式组合或选择它的输入。它在类型 a中是多态的,因此它将完全无法检查类型 a中的任何值,这意味着它通过组合 a值来完成“组合”,所以它必须完全根据型别构造器 f添加的任何结构来完成。

名字也有点帮助。某种“ OR”确实是作者试图用名称“ Alternative”和符号“ < | >”来表示的模糊概念。

现在,如果我有两个 Maybe a值,我必须将它们组合起来,我该怎么做?如果他们都是 Nothing,我将 返回 Nothing,没有办法创建一个 a。如果其中至少有一个是 Just ...,我可以返回一个输入原样,或者返回 Nothing。甚至很少有 有可能函数是 Maybe a -> Maybe a -> Maybe a类型的,对于一个名为“ Alternative”的类,给出的函数是非常合理和显而易见的。

如何组合两个 [a]值?这里有更多可能的函数,但实际上它可能做什么是非常明显的。“ Alternative”这个名字确实给了你一个很好的提示,告诉你 提供可能是什么样的。你熟悉单子/应用程序列表的标准“非确定性”解释; 如果你把 [a]看作是一个“非确定性 a”和一组可能的值,那么“结合两个非确定性 a值”的显而易见的方法就是产生一个非确定性 a,它可能是来自任何一个输入的任何值。

对于解析器来说,将两个解析器结合起来有两个明显的广义解释: 要么生成一个解析器来匹配第一个解析器的功能,然后匹配第二个解析器的功能; 要么生成一个解析器来匹配 都不是,匹配第一个解析器的功能,匹配 或者,匹配第二个解析器的功能(当然,每个选项都有一些微妙的细节,这些细节为选项留出了空。考虑到“ Alternative”这个名称,对于 <|>来说,“ or”的解释似乎非常自然。

因此,从足够高的抽象层次来看,这些操作 都“做同样的事情”。类型类实际上是用于在高层次的抽象中进行操作,这些事情都“看起来一样”。当我操作一个已知的实例时,我认为 <|>操作就是它对特定类型执行的操作。

这些 看起来非常不同,但请考虑:

Nothing <|> Nothing == Nothing
[] <|>      [] ==      []


Just a  <|> Nothing == Just a
[a] <|>      [] ==     [a]


Nothing <|> Just b  == Just b
[] <|>     [b] ==     [b]

所以... 这些实际上是非常非常 相似的,即使实现看起来不同。唯一真正的区别在于:

Just a  <|> Just b  == Just a
[a] <|>     [b] ==     [a, b]

Maybe只能保存 值(或者为零,但不能保存任何其他值)。但是,嘿,如果他们都是 一模一样,为什么你需要两种不同的类型?他们与众不同的关键就在于 与众不同

总之,实施可能看起来完全不同,但实际上它们非常相似。