Haskell: как произносится <*>? [закрыто]

109

Как произносятся эти функции в классе типов Applicative:

(<*>) :: f (a -> b) -> f a -> f b
(*>)  :: f a -> f b -> f b
(<*)  :: f a -> f b -> f a

(То есть, если бы они не были операторами, как бы их называть?)

В качестве примечания: если бы вы могли переименовать его pureво что-нибудь более дружелюбное для нематематиков, как бы вы это назвали?

Дж. Купер
источник
6
@J Cooper ... ты слышишь, как мы это произносим? :) Возможно, вы захотите опубликовать запрос на meta.stackoverflow.com для функции записи и воспроизведения голоса :).
Кирилл
8
Произносится: «Боже мой, у них действительно кончаются операторы, не так ли?» Кроме того, хорошее название pureможет быть makeApplicative.
Чак
@Lirik, ну, я предполагаю, что под произношением я подразумеваю "как бы называть эту штуку" :) @ Чак, опубликуйте свое pureпредложение в качестве ответа, и я поддержу вас
Дж. Купер,
6
(<*>) - это Control.Applicative версия «ap» Control.Monad, поэтому «ap», вероятно, наиболее подходящее имя.
Эдвард КМЕТТ
11
Я бы назвал это циклопом, но это только я.
RCIX

Ответы:

245

Извините, я плохо разбираюсь в математике, поэтому мне любопытно, как произносятся функции в классе типов Applicative

Я думаю, что знание вашей математики здесь в значительной степени не имеет значения. Как вы, вероятно, знаете, Haskell заимствует несколько терминов из различных областей абстрактной математики, в первую очередь из теории категорий , откуда мы и получаем функторы и монады. Использование этих терминов в Haskell несколько отличается от формальных математических определений, но обычно они достаточно близки, чтобы в любом случае быть хорошими описательными терминами.

В Applicative типа находится где-то между Functorи Monad, поэтому можно было бы ожидать, что он имеет аналогичную математическую основу. Документация к Control.Applicativeмодулю начинается с:

Этот модуль описывает промежуточную структуру между функтором и монадой: он обеспечивает чистые выражения и последовательность, но без привязки. (Технически это сильный нестрогий моноидальный функтор.)

Хм.

class (Functor f) => StrongLaxMonoidalFunctor f where
    . . .

MonadДумаю, не так запоминающе .

В основном все это сводится к тому, Applicativeчто не соответствует ни одной особенно интересной концепции. математической точки зрения, поэтому нет никаких готовых терминов, которые отражали бы способ ее использования в Haskell. Итак, отложим пока математику.


Если мы хотим знать, как позвонить (<*>) это может помочь узнать, что это в основном означает.

Так в чем дело Applicativeи почему? же мы называем это что?

На Applicativeпрактике получается способ поднять произвольные функции в Functor. Рассмотрим комбинацию Maybe(возможно, самого простого нетривиального Functor) и Bool(аналогично простейшего нетривиального типа данных).

maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not

Эта функция fmapпозволяет нам перейти notот работы Boolк работе Maybe Bool. Но что, если мы хотим поднять (&&)?

maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)

Ну, это не то , что мы хотим , чтобы у всех ! Фактически, это бесполезно. Мы можем попытаться быть умным и украдкой другой Boolв Maybeчерез спину ...

maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)

... но это нехорошо. Во-первых, это неправильно. Во-вторых, это некрасиво . Мы могли бы продолжать попытки, но оказывается, что есть никакого способа поднять функцию с несколькими аргументами для работы с произвольнымFunctor . Раздражает!

С другой стороны, мы могли бы сделать это легко , если бы мы использовали Maybe«s Monadэкземпляр:

maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
                  y' <- y
                  return (x' && y')

Теперь, это много хлопот просто перевести простую функцию - именно поэтому Control.Monadобеспечивает функцию , чтобы сделать это автоматически, liftM2. Цифра 2 в названии указывает на то, что он работает с функциями ровно с двумя аргументами; аналогичные функции существуют для функций с 3, 4 и 5 аргументами. Эти функции лучше , но не идеальны, а указывать количество аргументов некрасиво и неуклюже.

Это подводит нас к статье, в которой представлен класс типа Applicative. . В нем авторы делают по существу два наблюдения:

  • Преобразование функций с несколькими аргументами в Functor - это очень естественная вещь.
  • Для этого не требуются все возможности Monad

Обычное функциональное приложение написано простым сопоставлением терминов, поэтому, чтобы сделать «поднятое приложение» как можно более простым и естественным, в документе представлены инфиксные операторы, заменяющие приложение, перенесенные вFunctor класс, и класс типа, чтобы предоставить все необходимое для этого. .

Все это подводит нас к следующему пункту: (<*>)просто представляет собой приложение функции - так зачем произносить его иначе, чем вы произносите пробельный «оператор сопоставления»?

Но если это не очень удовлетворительно, мы можем заметить, что Control.Monadмодуль также предоставляет функцию, которая делает то же самое для монад:

ap :: (Monad m) => m (a -> b) -> m a -> m b

Где ap, конечно, короткий для «применить». Поскольку любой Monadможет быть Applicativeи apнуждается только в подмножестве функций, присутствующих в последнем, мы, возможно, можем сказать, что если бы он (<*>)не был оператором, его следовало бы вызвать ap.


Мы также можем подойти к вещам с другой стороны. Операция Functorподъема вызывается, fmapпотому что это обобщение mapоперации над списками. Какая функция в списках могла бы работать (<*>)? Конечно, есть то, что apесть в списках, но само по себе это не особенно полезно.

На самом деле, списки могут интерпретироваться более естественно. Что приходит в голову, когда вы смотрите на следующую подпись типа?

listApply :: [a -> b] -> [a] -> [b]

Есть что-то настолько заманчивое в идее выстраивания списков параллельно, применяя каждую функцию в первом к соответствующему элементу второго. К несчастью для нашего старого друга Monad, эта простая операция нарушает законы монад, если списки имеют разную длину. Но он приносит штраф Applicative, и в этом случае он (<*>)становится способом объединения обобщенной версии zipWith, так что, возможно, мы можем представить себе, как это называется fzipWith?


Эта идея застегивания на самом деле завершает круг. Помните ту математику о моноидальных функторах? Как следует из названия, это способ объединения структуры моноидов и функторов, оба из которых являются знакомыми классами типов Haskell:

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Monoid a where
    mempty :: a
    mappend :: a -> a -> a

Как бы они выглядели, если бы вы сложили их в коробку и немного встряхнули? Оттуда Functorмы сохраним идею структуры, независимой от ее параметра типа , а затем Monoidсохраним общую форму функций:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ?
    mfAppend :: f ? -> f ? -> f ?

Мы не хотим предполагать, что есть способ создать действительно «пустой» Functor, и мы не можем вызвать значение произвольного типа, поэтому мы исправим тип mfEmptyas f ().

Мы также не хотим, чтобы принудительно mfAppendтребовался параметр согласованного типа, поэтому теперь у нас есть это:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f ?

Для чего нужен тип результата mfAppend? У нас есть два произвольных типа, о которых мы ничего не знаем, поэтому у нас не так много вариантов. Самое разумное - просто сохранить оба:

class (Functor f) => MonoidalFunctor f where
    mfEmpty :: f ()
    mfAppend :: f a -> f b -> f (a, b)

В этот момент mfAppendтеперь ясно обобщенный вариант zipв списках, и мы можем реконструировать Applicativeлегко:

mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)

Это также показывает нам, что pureсвязано с элементом идентичности a Monoid, поэтому другими хорошими именами для него может быть что угодно, предлагающее значение единицы, нулевую операцию или что-то подобное.


Это было долго, так что резюмирую:

  • (<*>) - это просто модифицированное приложение-функция, поэтому вы можете прочитать его как «ap» или «применить», или полностью исключить его, как и обычное приложение-функцию.
  • (<*>)также примерно обобщает zipWithсписки, поэтому вы можете читать его как «zip-функторы с», аналогично чтениюfmap «сопоставить функтор с».

Первый ближе к замыслу Applicative класса типа - как следует из названия - так что я рекомендую.

Фактически, я призываю к либеральному использованию, а не к произношению всех операторов поднятого приложения :

  • (<$>), который переводит функцию с одним аргументом в Functor
  • (<*>), который связывает функцию с несколькими аргументами через Applicative
  • (=<<), который связывает функцию, которая вводит в Monadсуществующее вычисление

По сути, все три являются обычным функциональным приложением, немного приправленным.

CA McCann
источник
6
@ Колин Кокрейн: Вы уверены, что не ошиблись в написании слова "многословный"? :) Но эй, я возьму! Я всегда чувствую, что это Applicativeи функциональный идиоматический стиль, который он продвигает, не вызывают достаточной любви, поэтому я не мог устоять перед шансом немного превозносить его достоинства, чтобы объяснить, как я (не) произносю (<*>).
CA McCann
+1! Пожалуйста, не стесняйтесь быть столь же многословным на stackoverflow.com/questions/2104446/…
Грег Бэкон
6
Если бы у Haskell был синтаксический сахар для Applicative's! Что-то вроде [| f a b c d |](как предполагалось в исходной статье). Тогда нам не понадобится <*>комбинатор, и вы должны ссылаться на такое выражение как на пример «применения функции в функциональном контексте»
Том Крокетт
1
@FredOverflow: Нет, я имел в виду Monad. Или Functorили Monoidчто-нибудь еще, в котором есть устоявшийся термин, состоящий из менее чем трех прилагательных. «Аппликатив» - это просто скучное, хотя и достаточно информативное, название, наложенное на то, что скорее всего в нем нуждается.
CA McCann,
1
@pelotom: см. [ stackoverflow.com/questions/12014524/…, где добрые люди показали мне два способа получить почти такую ​​нотацию.
AndrewC
21

Поскольку у меня нет амбиций улучшить технический ответ CA McCann , я возьмусь за более пушистый:

Если бы вы могли переименовать его pureво что-нибудь более дружелюбное к подункам вроде меня, как бы вы это назвали?

В качестве альтернативы, особенно с учетом того, что нет конца постоянным воплям, наполненным тревогой и предательством, против Monadверсии, называемой " return", я предлагаю другое название, которое указывает на его функцию таким образом, чтобы удовлетворить самые императивные требования программистов. , и самый функциональный из ... ну, надеюсь, каждый может так же пожаловаться на:inject .

Примите значение. «Инъекция» это в Functor, Applicative, Monadили что-у-вы. Голосую за " inject", и я одобрил это сообщение.

BMeph
источник
4
Я обычно склоняюсь к чему-то вроде «единицы» или «подъема», но у них уже есть слишком много других значений в Haskell. inject- отличное имя и, вероятно, лучше, чем мое, хотя в качестве небольшого примечания, "inject" используется - я думаю - в Smalltalk и Ruby для какого-то метода левого свертывания. Я никогда не понимал этого выбора имени, хотя ...
CA McCann
3
Это очень старый поток, но я думаю, что он используется injectв Ruby и Smalltalk, потому что это как будто вы «вставляете» оператор между каждым элементом в списке. По крайней мере, я всегда так думал об этом.
Джонатан Стерлинг,
1
Чтобы снова взять этот старый побочный поток: вы не вводите операторы, вы заменяете (устраняете) конструкторы, которые уже существуют. (Рассматриваемое наоборот, вы инъекционная старые данные в новый тип.) Для списков, исключение составляет только foldr. (Вы заменяете (:)and [], где (:)принимает 2 аргумента и []является константой, следовательно, foldr (+) 0 (1:2:3:[])1+2+3+0.) На Boolнем просто if- then- else(две константы, выберите одну), и потому что Maybeон называется maybe… В Haskell нет единого имени / функции для этого, поскольку все они имеют разные типы (в общем, elim - это просто рекурсия / индукция)
nobody
@CAMcCann Smalltalk получил такое название из песни Арло Гатри о проекте для войны во Вьетнаме, в котором несчастных молодых людей отбирали, отбирали, иногда отвергали, а иногда и вводили.
Том Андерсон
7

Вкратце:

  • <*>вы можете назвать это применением . Так Maybe f <*> Maybe aможно произносить как « применить Maybe fнад»Maybe a .

  • Вы можете переименовать pureв of, как это делают многие библиотеки JavaScript. В JS вы можете создать файл с Maybeрасширением Maybe.of(a).

Также в вики Haskell есть страница о произношении языковых операторов здесь

Марсело Лазарони
источник
3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

Источник: Программирование на Haskell из первых принципов , Крис Аллен и Джули Моронуки.

dmvianna
источник
Насколько я могу судить, эти имена не совсем прижились.
dfeuer
@dfeuer ждет следующего поколения хаскеллеров, которые будут использовать эту книгу в качестве основного учебного материала.
dmvianna
1
Это могло случиться. Хотя имена ужасны, поскольку они не имеют никакого отношения к значениям.
dfeuer
1
@dfeuer, я нигде не вижу хорошего решения. «ap» / «apply» так же расплывчаты, как и «tie fighter». Все это функциональное приложение. Однако имя, появившееся неожиданно, могло обрести смысл благодаря использованию. «Яблоко» - хороший тому пример. Кстати, возврат Monad - это Аппликативный чистый. Здесь никаких изобретений.
dmvianna