Что делает оператор 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

Хорошо, поэтому он возвращается вправо, если нет левого, иначе он возвращается влево, gotcha.. Это заставляет меня считать его "левым или правым" оператором, что имеет смысл с учетом использования | и | историческое использование как "ИЛИ"

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

За исключением этого он просто вызывает оператор конкатенации списков... Прерывая мою идею...

Так что же это за функция? Каково его использование? Где он вписывается в великую схему вещей?

Ответ 1

Обычно это означает "выбор" или "параллель" в том, что a <|> b является либо "выбором" a, либо b или a и b выполняется параллельно. Но пусть резервное копирование.

Действительно, практического смысла в операциях в классах типа (<*>) или (<|>) нет. Эти операции дают смысл двумя способами: (1) через законы и (2) посредством инстанцирования. Если мы не говорим о конкретном экземпляре Alternative, то для интуитивного значения доступно только (1).

Итак, "ассоциативный" означает, что a <|> (b <|> c) совпадает с (a <|> b) <|> c. Это полезно, так как это означает, что мы заботимся о последовательности вещей, соединенных вместе с (<|>), а не их "древовидной структурой".

Другие законы включают идентификацию с empty. В частности, a <|> empty = empty <|> a = a. В нашей интуиции с "выбором" или "параллельным" эти законы, считанные как "а (или что-то невозможное), должны быть" или "рядом (пустой процесс) - это просто". Это означает, что empty является своего рода "режимом отказа" для Alternative.

Существуют другие законы, с которыми (<|>)/empty взаимодействует с fmap (от Functor) или pure/(<*>) (от Applicative), но, возможно, лучший способ двигаться вперед в понимании значения (<|>) следует рассмотреть очень распространенный пример типа, который создает экземпляр Alternative: a Parser.

Если x :: Parser A и y :: Parser B, то (,) <$> x <*> y :: Parser (A, B) анализирует x и затем y в последовательности. Напротив, (fmap Left x) <|> (fmap Right y) анализирует либо x, либо y, начиная с x, чтобы опробовать оба возможных анализа. Другими словами, он указывает ветвь в дереве синтаксического анализа, выборе или параллельном синтаксическом разборе.

Ответ 2

(<|>) :: f a -> f a -> f a действительно говорит вам довольно много, даже не учитывая законы для Alternative.

Он принимает два значения f a и должен дать один ответ. Поэтому он должен будет каким-то образом объединить или выбрать из своих входов. Он является полиморфным в типе a, поэтому он не сможет полностью проверить, какие значения типа a могут находиться внутри f a; это означает, что он не может "комбинировать", комбинируя значения a, поэтому он должен быть в нем чисто с точки зрения любой структуры, добавляемой конструктором типа f.

Название также помогает. Какой-то "OR" - это действительно неопределенное понятие, которое авторы пытались указать с именем "Альтернатива" и символом "< | > ".

Теперь, если у меня есть два значения Maybe a, и я должен их объединить, что я могу сделать? Если они оба Nothing, мне придется возвращать Nothing, не имея возможности создать a. Если хотя бы один из них является Just ..., я могу вернуть один из моих входов как есть, или я могу вернуть Nothing. Существует очень мало функций, которые даже возможны с типом Maybe a -> Maybe a -> Maybe a, а для класса, имя которого является "Альтернативным", это достаточно разумно и очевидно.

Как насчет объединения двух значений [a]? Здесь есть более возможные функции, но на самом деле довольно очевидно, что это может сделать. И название "Альтернатива" дает вам хороший намек на то, что это может произойти, если вы знакомы со стандартной интерпретацией "недетерминизма" списка монады/аппликативного; если вы видите [a] как "недетерминированный a" с набором возможных значений, то очевидный способ "объединения двух недетерминированных значений a" таким образом, чтобы заслужить имя "Альтернатива", - это создать неопределенный a, который может быть любым из значений от любого из входов.

И для парсеров; объединение двух парсеров имеет две очевидные широкие интерпретации, которые spring должны учитывать; либо вы создаете парсер, который будет соответствовать тому, что делает первый, а затем то, что делает второй, или вы создаете парсер, который соответствует либо тому, что делает первый, либо тому, что делает второй (есть, конечно, тонкие детали каждого из этих вариантов, которые уходят комната для опций). Учитывая название "Альтернатива", интерпретация "или" представляется очень естественной для <|>.

Таким образом, из-за достаточно высокого уровня абстракции эти операции делают все "то же самое". Класс типа действительно предназначен для работы на таком высоком уровне абстракции, где все эти вещи "выглядят одинаково". Когда я работаю в одном известном экземпляре, я просто думаю об операции <|> как о том, что он делает для этого конкретного типа.

Ответ 3

Интересным примером Alternative, который не является парсером или подобной MonadPlus, является Concurrently, очень полезный введите из пакета async.

Для Concurrently, empty - это вычисление, которое продолжается вечно. И (<|>) выполняет свои аргументы одновременно, возвращает результат первого, который завершает, и отменяет другой.

Ответ 4

Это кажется совсем другим, но учтите:

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]

A Maybe может содержать только одно значение (или ноль, но не любую другую сумму). Но эй, если бы они были одинаковыми, зачем вам нужны два разных типа? Вы знаете, что все они отличаются друг от друга.

Таким образом, реализация может выглядеть совершенно иначе, но на самом деле они очень похожи.