Haskell: Як вимовляється <*>? [зачинено]


109

Як ви вимовляєте ці функції в типовому класі Applica:

(<*>) :: 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, Ну, мабуть, під висловом я маю на увазі "whaddya call this thing" :) @ Чак, опублікуй свою pureпропозицію як відповідь, і я підтримаю вас
J Cooper

6
(<*>) є Control.Applicative версія Control.Monad "ap", тому "ap", мабуть, найбільш відповідне ім'я.
Едвард КМЕТТ

11
Я б назвав це циклопом, але це тільки я.
RCIX

Відповіді:


245

Вибачте, я дійсно не знаю своєї математики, тому мені цікаво, як вимовляти функції в типовому класі Applicative

Знаю свою математику чи ні, я вважаю, що це значною мірою не має значення. Як ви, напевно, знаєте, Хаскелл запозичує декілька біт термінології з різних областей абстрактної математики, особливо це стосується теорії категорій , звідки ми отримуємо функціонерів та монадів. Використання цих термінів у Haskell дещо відрізняється від формальних математичних визначень, але вони, як правило, достатньо близькі, щоб у будь-якому разі були хорошими описовими термінами.

Клас Applicativeтипу сидить десь між Functorі Monad, тому можна було б очікувати, що він має подібну математичну основу. Документація для Control.Applicativeмодуля починається з:

Цей модуль описує проміжну структуру між функтором та монадою: він забезпечує чисті вирази та послідовності, але не зв'язує. (Технічно - сильний в'ялий моноїдний функтор.)

Хм.

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

Я не зовсім такий привабливий, як Monadя думаю.

В основному все це зводиться до того, що Applicativeне відповідає жодній цікавій математичній концепції , тому немає готових термінів, які б захоплювали те, як це використовується в Haskell. Отже, відкладіть математику поки що.


Якщо ми хочемо знати, як зателефонувати, (<*>)це може допомогти дізнатися, що це в основному означає.

Так що з Applicative, у всякому разі, і чому ж ми називаємо це що?

Що Applicativeна практиці означає спосіб підняття довільних функцій на a 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 . У ній автори роблять по суті два спостереження:

  • Підняття багатоаргументних функцій на a 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, і ми не можемо викликати в уяві значення довільного типу, тому ми будемо фіксувати тип , mfEmptyяк 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пов'язаний з елементом ідентичності а Monoid, тому інші хороші назви для нього можуть бути будь-якими, що підказують одиничне значення, нульову операцію тощо.


Це було тривалим, так підсумовуючи:

  • (<*>) - це лише модифікована функціональна програма, тож ви можете її прочитати як "ap" або "застосувати", або повністю відключити її так, як це було б у звичайній програмі.
  • (<*>)також орієнтовно узагальнюється zipWithза списками, так що ви можете читати його як "поштові функтори з", подібно до читання fmapяк "карта функтора з".

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

Насправді, я закликаю до ліберального використання та невимовлення всіх операторів, що підняли додаток :

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

Усі троє, по суті, просто звичайне застосування функцій, трохи приправлене.


6
@Colin Cochrane: Ви впевнені, що там не помилялися з помилкою "довгодушних"? :) Але ей, я візьму! Я завжди відчуваю, що 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", і я схвалив це повідомлення.


4
Зазвичай я схиляюся до чогось типу "агрегат" або "підйомник", але в них вже є занадто багато інших значень у Haskell. injectце чудове ім'я і, мабуть, краще, ніж моє, хоча як незначна бічна примітка, "ін'єкція" використовується в - я думаю - Smalltalk і Ruby для якогось методу з лівою складкою. Я ніколи не розумів цього вибору імені ...
CA McCann

3
Це дуже стара нитка, але я думаю, що injectв Ruby & Smalltalk використовується, оскільки це так, як ти "вводиш" оператора між кожним елементом у списку. Принаймні, так я завжди про це думав.
Джонатан Стерлінг

1
Щоб знову підібрати цю стару бічну нитку: Ви не вводить операторів, ви замінюєте (усуваєте) конструктори, які вже є. (Розглядається навпаки, ви ін'єкційна старі дані в новий тип.) Для списків, виключення складає тільки foldr. (Ви замінюєте (:)і [], де (:)займає 2 аргументи, і []це константа, отже, foldr (+) 0 (1:2:3:[])1+2+3+0.) BoolЦе просто if- then- else(дві константи, виберіть одну) і для Maybeцього називається maybe… Haskell не має для цього єдиного імені / функції, оскільки всі мають різні типи (загалом elim - це просто рекурсія / індукція)
ніхто

@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 з перших принципів , Кріс Аллен та Джулі Моронукі


Наскільки я не можу сказати, ці імена точно не прижилися.
dfeuer

@dfeuer чекайте наступного покоління Haskellers, які використовують цю книгу як основний навчальний матеріал.
dmvianna

1
Це могло статися. Назви, однак, жахливі, оскільки не мають зв'язку зі значеннями.
dfeuer

1
@dfeuer, я ніде не бачу гарного рішення. "ap" / "застосовувати" так само невиразно, як і "борець за краватку". Все - це застосування функції. Однак назва, що виходить із синього кольору, може набути значення через використання. "Яблуко" - хороший приклад. До речі, повернення Монади є додатково чистим. Тут немає винаходів.
dmvianna
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.