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 = []
(<|>) = (++)
_
ここを除いて、リストの連結演算子を呼び出します...私のアイデアを分解します...
それで、その機能は正確に何ですか?その用途は何ですか?物事の壮大な計画のどこに収まるのでしょうか?
通常、a <|> b
はa
またはb
またはa
およびb
の「選択」であるという点で、「選択」または「並列」を意味します。しかし、バックアップしましょう。
本当に、(<*>)
や(<|>)
のような型クラスの操作には実用的な意味はありません。これらの操作には、2つの方法で意味が与えられます:(1)法律による方法と(2)インスタンス化による方法。 Alternative
の特定のインスタンスについて話していない場合、意味を直感的に理解できるのは(1)のみです。
したがって、「連想」とは、a <|> (b <|> c)
が(a <|> b) <|> c
と同じであることを意味します。これは、「ツリー構造」ではなく、(<|>)
でチェーン化されたもののsequenceのみを対象とするため、便利です。
他の法律には、empty
との同一性が含まれます。特に、a <|> empty = empty <|> a = a
。 「選択」または「並行」という直観では、これらの法則は「aまたは(不可能なもの)でなければならない」または「a(空のプロセス)はただa」と読みます。これは、empty
がAlternative
のある種の「障害モード」であることを示しています。
(<|>)
/empty
がfmap
(Functor
から)または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を示します。
(<|>) :: f a -> f a -> f a
は、Alternative
の法則を考慮しなくても、実際に多くのことを伝えます。
2つのf a
値を取り、1つを返さなければなりません。そのため、何らかの方法で入力を結合または選択する必要があります。タイプa
には多態性があるため、タイプa
の値がf a
内にある可能性があることを完全に検査することはできません。これは、できないa
値を組み合わせて「結合」を行うことを意味するため、タイプコンストラクターf
が追加する構造に関して純粋にそれを行う必要があります。
名前も少し役立ちます。ある種の「OR」は、著者が「代替」という名前と記号「<|>」で示すことを試みた曖昧な概念です。
2つのMaybe a
値があり、それらを結合する必要がある場合、どうすればよいですか?両方がNothing
である場合、haveをNothing
を返しますが、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はすべて「同じことをする」。型クラスは、これらのすべてが「同じように見える」高レベルの抽象化で実際に動作するためのものです。単一の既知のインスタンスを操作しているとき、<|>
操作は、その特定の型に対して行うこととまったく同じと考えています。
これらの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つの異なるタイプが必要なのでしょうか?それらが異なる点は、ご存知のとおり、異なるようにです。
要約すると、実装はまったく異なるように見えるかもしれませんが、これらは実際には非常によく似ています。
パーサーやMonadPlusのようなものではないAlternative
の興味深い例は、 Concurrently
、 async
パッケージの非常に便利なタイプ。
Concurrently
の場合、empty
は永遠に続く計算です。そして(<|>)
は、引数を同時に実行し、最初の引数の結果を返し、完了した引数をキャンセルします。