web-dev-qa-db-ja.com

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
_

OK、それで左がない場合は右を返し、そうでない場合は左を返します、これは「左または右」演算子であると信じさせます。これは、_|_および_|_の「OR」としての歴史的使用

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

ここを除いて、リストの連結演算子を呼び出します...私のアイデアを分解します...

それで、その機能は正確に何ですか?その用途は何ですか?物事の壮大な計画のどこに収まるのでしょうか?

64
Electric Coffee

通常、a <|> baまたはbまたはaおよびbの「選択」であるという点で、「選択」または「並列」を意味します。しかし、バックアップしましょう。

本当に、(<*>)(<|>)のような型クラスの操作には実用的な意味はありません。これらの操作には、2つの方法で意味が与えられます:(1)法律による方法と(2)インスタンス化による方法。 Alternative特定のインスタンスについて話していない場合、意味を直感的に理解できるのは(1)のみです。

したがって、「連想」とは、a <|> (b <|> c)(a <|> b) <|> cと同じであることを意味します。これは、「ツリー構造」ではなく、(<|>)でチェーン化されたもののsequenceのみを対象とするため、便利です。

他の法律には、emptyとの同一性が含まれます。特に、a <|> empty = empty <|> a = a。 「選択」または「並行」という直観では、これらの法則は「aまたは(不可能なもの)でなければならない」または「a(空のプロセス)はただa」と読みます。これは、emptyAlternativeのある種の「障害モード」であることを示しています。

(<|>)/emptyfmapFunctorから)またはpure/(<*>)Applicativeから)と対話する方法には他の法則もありますが、おそらく(<|>)の意味を理解する上で前進する最善の方法は、型の非常に一般的な例を調べることですAlternativeをインスタンス化します:Parser

x :: Parser Aおよびy :: Parser Bの場合、(,) <$> x <*> y :: Parser (A, B)xを解析し、次にyを順番に解析します。対照的に、(fmap Left x) <|> (fmap Right y)は、eitherxまたはyで始まるxのいずれかを解析し、可能な両方の解析を試行します。つまり、解析ツリー、選択、または並列解析ユニバースのbranchを示します。

66
J. Abrahamson

(<|>) :: f a -> f a -> f aは、Alternativeの法則を考慮しなくても、実際に多くのことを伝えます。

2つのf a値を取り、1つを返さなければなりません。そのため、何らかの方法で入力を結合または選択する必要があります。タイプaには多態性があるため、タイプaの値がf a内にある可能性があることを完全に検査することはできません。これは、できないa値を組み合わせて「結合」を行うことを意味するため、タイプコンストラクターfが追加する構造に関して純粋にそれを行う必要があります。

名前も少し役立ちます。ある種の「OR」は、著者が「代替」という名前と記号「<|>」で示すことを試みた曖昧な概念です。

2つのMaybe a値があり、それらを結合する必要がある場合、どうすればよいですか?両方がNothingである場合、haveNothingを返しますが、aを作成する方法はありません。それらの少なくとも1つがJust ...である場合、入力の1つをそのまま返すか、Nothingを返すことができます。タイプがMaybe a -> Maybe a -> Maybe aであるpossibleである関数はほとんどありません。また、名前が "Alternative"であるクラスの場合、指定された関数はかなり合理的で明白です。

2つの[a]値を組み合わせてどうですか?ここにはより多くの機能がありますが、実際にこれが何をする可能性があるかは明らかです。また、「代替」という名前は、これが何に関連する可能性があるかについての良いヒントを与えてくれます提供あなたはリストモナド/適用の標準的な「非決定論的」解釈に精通しています。 [a]が可能な値のコレクションを持つ「非決定的a」として表示される場合、当然の方法で「2つの非決定的a値を組み合わせる」明白な方法「代替」という名前は、いずれかの入力からの任意の値である可能性がある非決定的なaを生成することです。

そしてパーサー用。 2つのパーサーを組み合わせると、2つの明らかな広範な解釈が思い浮かびます。最初の操作と次に2番目の操作に一致するパーサーを作成するか、either最初の操作または 2番目の操作に一致するパーサーを作成します(もちろん、これらの各オプションには微妙な詳細があり、オプションの余地があります)。 「代替」という名前を考えると、<|>の「または」解釈は非常に自然に思えます。

したがって、十分に高いレベルの抽象化から見ると、これらの操作doはすべて「同じことをする」。型クラスは、これらのすべてが「同じように見える」高レベルの抽象化で実際に動作するためのものです。単一の既知のインスタンスを操作しているとき、<|>操作は、その特定の型に対して行うこととまったく同じと考えています。

35
Ben

これらのseemは非常に異なりますが、考慮してください:

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は、one値(またはゼロ、ただし他の量ではない)のみを保持できます。しかし、両方とも同一である場合、なぜ2つの異なるタイプが必要なのでしょうか?それらが異なる点は、ご存知のとおり、異なるようにです。

要約すると、実装はまったく異なるように見えるかもしれませんが、これらは実際には非常によく似ています。

12

パーサーやMonadPlusのようなものではないAlternativeの興味深い例は、 Concurrentlyasyncパッケージの非常に便利なタイプ。

Concurrentlyの場合、emptyは永遠に続く計算です。そして(<|>)は、引数を同時に実行し、最初の引数の結果を返し、完了した引数をキャンセルします。

12
danidiaz