Haskell的<|>运算符做什么?


69

对于我而言,仔细阅读Haskell的文档总是很痛苦,因为您所获得的有关某个功能的所有信息通常仅不过是:f a -> f [a]这可能意味着很多事情。

<|>功能一样。

我所得到的就是:(<|>) :: f a -> f a -> f a这是“关联二进制操作” ...

通过检查,Control.Applicative我了解到它确实与实现无关,取决于实现。

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

好的,所以如果没有左,它就返回右,否则它就返回左,陷阱。。这使我相信它是一个“左或右”运算符,考虑到它的使用||作为“或”的历史用法,这有点有意义”

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

除了这里,它只是调用list的串联运算符...分解我的想法...

那么那个功能到底是什么呢?有什么用?它在宏伟的计划中适合什么地方?


7
对于极通用的操作(例如),几乎不需要什么<|>。“理解”它们的正确方法是理解具体实例。也就是说,如果你想使用<|>Maybe,那么了解它的Maybe...
kosmikus

4
好吧,这是<>a的操作,Monoid它在中是多态的a
chaosmasttter

3
列表实例是因为将列表解释为元素的非确定性选择。如果您读<|>的意思是“ orelse”,那么对于两个不确定性列表,您将从其中任何一个获得不确定性可能性(++)
AndrewC 2014年

1
啊,但这就是您所需要知道的。与单元关联的二进制运算是一个monoid。List monoid具有一个空列表,作为一个单位和(++)关联的二进制操作。
Sassa NF 2014年

@SassaNF但是您可以为列表la ZipList至少创建两个完全不同的Applicative&Alternative实例。看到这个问题
AndrewC 2014年

Answers:


74

通常,它的意思是“选择”或“平行”,因为a <|> b或者是的“选择”abab并行完成。但是,让我们备份一下。

确实,类型类(<*>)或的操作没有任何实际意义(<|>)。这些操作有两种含义:(1)通过定律和(2)通过实例化。如果我们不是在谈论的特定实例,Alternative那么只有(1)可以理解。

因此,“关联”的含义a <|> (b <|> c)与相同(a <|> b) <|> c。这很有用,因为这意味着我们只关心与链接在一起的事物的顺序(<|>),而不关心它们的“树结构”。

其他法律包括与的身份empty。特别是a <|> empty = empty <|> a = a。在我们对“选择”或“平行”的直觉中,这些定律读为“一个或(某些不可能的东西)一定是一个”或“一个并排(空过程)只是一个”。它表示empty某种“故障模式” Alternative

还有其他关于(<|>)/emptyfmap(来自Functor)互动或与之互动的法律pure/ (<*>)(from Applicative),但也许最好的方法是理解(<|>)一个实例化类型的非常常见的示例Alternative:a Parser

如果x :: Parser Ay :: Parser B然后(,) <$> x <*> y :: Parser (A, B)解析x ,然后 y按顺序排列。相比之下,(fmap Left x) <|> (fmap Right y)解析无论是 xy,年初x,尝试两种可能的解析。换句话说,它表示解析树中的一个分支,一个选择或一个并行解析Universe。


但对于<*>,它或多或少总是做同样的事情。(Just (+3)) <*> (Just 4)Just 7[(+3)] <*> [1..3][4,5,6]你一样会从预期map操作。实例之间的含义并没有真正改变
Electric Coffee

5
@ElectricCoffee严格来说不是这样。例如,首先考虑类型ZipList a与基本上相同[a]。现在[(+1),(+2)] <*> [1,2] = [2,3,3,4]一会儿ZipList [(+1),(+2)] <*> ZipList [1,2] = ZipList [2,4]。因此,并不总是那么清楚到底(<*>)意味着什么。
J. Abrahamson 2014年

1
我只是想建议,的含义(<*>)具有共同的主题,是的-这些主题是通过仔细研究其规律而暴露出来的,但是我很难忍受它在实例上所做的基本相同。
J. Abrahamson 2014年

有人展示了一个很好的例子foldl1(<|>)可以实际展示它的一些功能
电动咖啡

1
的关联性(<|>)意味着折痕是有用的。折叠会“修复”线性树结构(完全左嵌套或完全右嵌套),并且仅由于关联性,我们知道我们可以忽略这种可能的重排的含义。
J. Abrahamson

37

(<|>) :: f a -> f a -> f a 实际上告诉了你很多,即使不考虑法律 Alternative

它需要两个f a值,并且必须还一。因此,它将不得不以某种方式组合或从其输入中进行选择。它在类型上是多态的a,因此它将完全无法检查;中a可能存在的任何类型的值f a。这意味着它不能通过组合a值来进行“组合” ,因此它必须纯粹根据类型构造函数f添加的任何结构来实现。

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

现在,如果我有两个Maybe a值并且必须将它们组合起来,该怎么办?如果两者都是,Nothing我将必须返回Nothing,而无法创建一个a。如果其中至少一个是a,Just ...我可以原样返回我的输入之一,或者我可以返回Nothing。有迹象表明,即使是很少的功能可能与类型Maybe a -> Maybe a -> Maybe a对于名称为“ Alternative”的类,给出的功能是相当合理且显而易见的。

如何结合两个 [a]值?这里有更多可能的功能,但实际上很明显这可能会做什么。如果您熟悉monad / applicative列表的标准“非确定性”解释,“ Alternative”这个名称确实为您提供了一个很好的暗示。如果您看到带有可能值集合的[a]“不确定性a”,那么以a可能值得命名为“替代”的方式“组合两个不确定性值”的明显方法是产生不确定性a,该不确定性可以是任何值从任何一个输入。

对于解析器;结合两个解析器,可以想到两个显而易见的广泛解释;要么生成与第一个匹配的解析器,然后匹配第二个匹配的解析器,要么生成一个匹配的解析器无论什么第一呢还是什么,第二个呢(也有每个选项是假的当然微妙的细节选择余地)。给定名称“替代”,“或”的解释似乎很自然<|>

所以,从一个足够高的抽象层次看出,这些操作所有“做同样的事情。” 类型类实际上是用于在所有这些“看起来都一样”的高级抽象层次上进行操作的。当我在单个已知实例上进行<|>操作时,我只是认为该操作与该特定类型的操作完全相同。


这些成分仅仅是类型类的名称,运算符的类型签名以及特定实例的一些具体示例。结果是了解Alternative类型类的方法。我想知道这种方法是否足以解释其他广义的FP概念。谢谢!

14

这些看起来很不一样,但请考虑:

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]

AMaybe只能保留一个值(或零,但不能保留其他任何值)。但是,如果它们都是相同的,那么为什么需要两种不同的类型?你知道他们不同的全部是不同的

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


12

一个Alternative非解析器或类似MonadPlus的东西的有趣示例是,这是Concurrently来自async包装中。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.