Haskell: Jak wymawia się <*>? [Zamknięte]

109

Jak wymawia się te funkcje w typeklasie Applicative:

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

(To znaczy, gdyby nie byli operatorami, jak mogliby się nazywać?)

Na marginesie, gdybyś mógł zmienić nazwę purena coś bardziej przyjaznego dla nie-matematyków, jak byś to nazwał?

J Cooper
źródło
6
@J Cooper ... czy mógłbyś usłyszeć, jak to wymawiamy? :) Możesz zamieścić prośbę na meta.stackoverflow.com o funkcję nagrywania i odtwarzania głosu :).
Kiril,
8
Mówi się: „Dobry Boże, naprawdę brakowało im operatorów, prawda?” Również dobre imię puremoże być makeApplicative.
Chuck,
@Lirik, Cóż, myślę, że przez wymówienie mam na myśli "whaddya call this thing" :) @Chuck, opublikuj swoją puresugestię jako odpowiedź, a ja cię zagłosuję
J Cooper
6
(<*>) to Control.Applicative wersja „ap” Control.Monad, więc „ap” jest prawdopodobnie najbardziej odpowiednią nazwą.
Edward KMETT
11
nazwałbym to cyklopem, ale to tylko ja.
RCIX

Odpowiedzi:

245

Przepraszam, naprawdę nie znam swojej matematyki, więc jestem ciekawy, jak wymawiać funkcje w typeklasie Applicative

Myślę, że znajomość matematyki, czy nie, jest tutaj w dużej mierze nieistotna. Jak zapewne wiesz, Haskell pożycza kilka fragmentów terminologii z różnych dziedzin abstrakcyjnej matematyki, w szczególności z teorii kategorii , skąd mamy funktory i monady. Użycie tych terminów w Haskell różni się nieco od formalnych definicji matematycznych, ale i tak są one na tyle bliskie, że i tak są dobrymi terminami opisowymi.

ApplicativeKlasa typ siedzi gdzieś pomiędzy Functori Monadtak można by oczekiwać, że mają podobną podstawę matematycznego. Dokumentacja Control.Applicativemodułu zaczyna się od:

Moduł ten opisuje strukturę pośrednią między funktorem a monadą: dostarcza czystych wyrażeń i sekwencjonowania, ale bez wiązania. (Technicznie, silny, luźny, monoidalny funktor.)

Hmm.

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

MonadMyślę, że nie tak chwytliwe, jak .

Wszystko to w zasadzie sprowadza się do tego, Applicativeże nie pasuje do żadnej szczególnie interesującej koncepcji matematycznie, więc nie ma gotowych terminów, które opisują sposób, w jaki jest używany w Haskell. Więc na razie odłóż na bok matematykę.


Jeśli chcemy wiedzieć, jak zadzwonić (<*>) , może pomóc wiedzieć, co to w zasadzie oznacza.

Więc o co chodzi Applicativei dlaczego tak to nazywamy?

Co Applicativewynosi w praktyce jest to sposób, aby podnieść dowolne funkcje w Functor. Rozważmy kombinację Maybe(prawdopodobnie najprostszy nietrywialny Functor) i Bool(podobnie jak najprostszy nietrywialny typ danych).

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

Ta funkcja fmappozwala nam podnieść się notz pracy Booldo pracy Maybe Bool. Ale co, jeśli chcemy podnieść (&&)?

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

Cóż, to nie jest to, co chcemy w ogóle ! W rzeczywistości jest to prawie bezużyteczne. Możemy starać się być mądry i podkraść innego Booldo Maybew tył ...

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

... ale to nie jest dobre. Po pierwsze, jest źle. Po drugie, jest brzydki . Moglibyśmy próbować dalej, ale okazuje się, że nie ma sposobu, aby podnieść funkcję wielu argumentów do pracy na dowolnejFunctor . Denerwujący!

Z drugiej strony, możemy to zrobić z łatwością, jeśli użyliśmy Maybe„s Monadinstancji:

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

Teraz to dużo kłopotów, żeby przetłumaczyć prostą funkcję - dlatego Control.Monadzapewnia funkcję, aby to zrobić automatycznie liftM2. Dwójka w nazwie odnosi się do faktu, że działa na funkcjach o dokładnie dwóch argumentach; podobne funkcje istnieją dla funkcji 3, 4 i 5 argumentowych. Te funkcje są lepsze , ale nie doskonałe, a określanie liczby argumentów jest brzydkie i niezdarne.

To prowadzi nas do artykułu, który wprowadził klasę typu Applicative . W nim autorzy dokonują zasadniczo dwóch obserwacji:

  • Przeniesienie funkcji wieloargumentowych do a Functorjest rzeczą bardzo naturalną
  • Nie wymaga to pełnych możliwości pliku Monad

Normalna aplikacja funkcji jest napisana przez proste zestawienie terminów, więc aby uczynić "podniesioną aplikację" tak prostą i naturalną, jak to tylko możliwe, w artykule przedstawiono operatory wrostków, które zastępują aplikację, przeniesione doFunctor i klasy typu, aby zapewnić to, co jest do tego potrzebne .

Wszystko to prowadzi nas do następującego punktu: (<*>)po prostu reprezentuje aplikację funkcji - dlaczego więc wymawiać ją inaczej niż „operator zestawienia” z białymi znakami?

Ale jeśli to nie jest zbyt satysfakcjonujące, możemy zauważyć, że Control.Monadmoduł udostępnia również funkcję, która robi to samo dla monad:

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

Gdzie apjest oczywiście skrótem od „aplikuj”. Ponieważ każdy Monadmoże być Applicativei appotrzebuje tylko podzbioru funkcji obecnych w tym drugim, możemy być może powiedzieć, że gdyby (<*>)nie był operatorem, należałoby go wywołać ap.


Możemy też podejść do rzeczy z innej strony. Operacja Functorpodnoszenia nazywa się, fmapponieważ jest uogólnieniem mapoperacji na listach. Jak wyglądałaby funkcja na listach (<*>)? Jest coap działa na listach, ale samo w sobie nie jest to szczególnie przydatne.

W rzeczywistości istnieje być może bardziej naturalna interpretacja list. Co przychodzi na myśl, gdy patrzysz na następujący podpis typu?

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

Jest coś tak kuszącego w pomyśle równoległego zestawiania list i przypisywania każdej funkcji z pierwszej do odpowiedniego elementu drugiej. Niestety dla naszego starego przyjaciela Monad, ta prosta operacja narusza prawa monady, jeśli listy mają różną długość. Ale to dobrze Applicative, w takim przypadku (<*>)staje się sposobem na połączenie razem uogólnionej wersji zipWith, więc może możemy sobie wyobrazić nazywanie tego fzipWith?


Ten pomysł na spakowanie faktycznie prowadzi nas do pełnego koła. Pamiętasz te matematyczne rzeczy wcześniej, o monoidalnych funktorach? Jak sama nazwa wskazuje, są to sposoby łączenia budowy monoidów i funktorów, z których oba są znanymi klasami typu Haskell:

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

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

Jak by wyglądały, gdybyś umieścił je razem w pudełku i trochę nim potrząsnął? Z Functortego miejsca zachowamy ideę struktury niezależnej od jej parametru typu , a od Monoidtego zachowamy ogólną postać funkcji:

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

Nie chcemy zakładać, że istnieje sposób na stworzenie prawdziwie „pustego” Functori nie możemy wyczarować wartości dowolnego typu, więc naprawimy typ mfEmptyas f ().

Nie chcemy również wymuszać mfAppendkonieczności posiadania spójnego parametru typu, więc teraz mamy to:

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

Jaki jest typ wyniku mfAppend? Mamy dwa dowolne typy, o których nic nie wiemy, więc nie mamy wielu opcji. Najrozsądniej jest zachować jedno i drugie:

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

W którym momencie mfAppendjest teraz wyraźnie uogólniona wersja ziplisty i możemy Applicativełatwo zrekonstruować :

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

To również pokazuje nam, że purejest powiązany z elementem tożsamości a Monoid, więc innymi dobrymi nazwami może być wszystko, co sugeruje wartość jednostkową, operację zerową lub tym podobne.


To było długie, więc podsumowując:

  • (<*>) jest po prostu zmodyfikowaną aplikacją funkcyjną, więc możesz ją odczytać jako „ap” lub „zastosować”, lub całkowicie usunąć ją w taki sam sposób, jak zwykłą aplikację funkcyjną.
  • (<*>)również z grubsza uogólnia zipWithlisty, więc można to czytać jako „funktory zip z”, podobnie do czytania fmapjako „mapuj funktor z”.

Pierwsza jest bliższa intencji Applicativeklasy typu - jak sama nazwa wskazuje - więc to właśnie polecam.

W rzeczywistości zachęcam do liberalnego używania i braku wymowy wszystkich operatorów podniesionych aplikacji :

  • (<$>), która przenosi funkcję jednoargumentową do pliku Functor
  • (<*>), który łączy funkcję wieloparametrową za pomocą pliku Applicative
  • (=<<), który wiąże funkcję, która wprowadza a Monaddo istniejącego obliczenia

Wszystkie trzy są w istocie zwykłymi aplikacjami funkcyjnymi, nieco doprawionymi.

CA McCann
źródło
6
@Colin Cochrane: Czy na pewno nie przeliterowałeś tam „rozwlekły”? :) Ale hej, wezmę to! Zawsze to czuję, Applicativea funkcjonalny styl idiomatyczny, który promuje, nie daje wystarczającej miłości, więc nie mogłem oprzeć się szansie, aby trochę wychwalać jego zalety, aby wyjaśnić, jak (nie) wymawiam (<*>).
CA McCann
+1! Nie krępuj się być podobnie rozwlekłym na stackoverflow.com/questions/2104446/ ...
Greg Bacon,
6
Czy Haskell miałby cukier składniowy dla Applicative! Coś w rodzaju [| f a b c d |](jak sugerował oryginalny artykuł). Wtedy nie potrzebowalibyśmy <*>kombinatora i odnosiłbyś się do takiego wyrażenia jako przykładu „zastosowania funkcji w kontekście funktorskim”
Tom Crockett,
1
@FredOverflow: Nie, miałem na myśli Monad. Lub Functorlub Monoidcokolwiek innego, co ma ugruntowany termin obejmujący mniej niż trzy przymiotniki. „Applicative” to po prostu mało inspirująca, aczkolwiek dość opisowa nazwa narzucona na coś, co raczej jej potrzebowało.
CA McCann
1
@pelotom: zobacz [ stackoverflow.com/questions/12014524/ ... gdzie mili ludzie pokazali mi dwa sposoby uzyskania prawie tego zapisu.
AndrewC
21

Ponieważ nie mam ambicji ulepszania technicznej odpowiedzi CA McCann , zajmę się bardziej puszystą:

Gdybyś mógł zmienić nazwę purena bardziej przyjazną dla podunków takich jak ja, jak byś to nazwał?

Jako alternatywę, zwłaszcza że nie ma końca nieustannym, pełnym lęku i zdrady wołaniu przeciwko Monadwersji, zwanej " return", proponuję inną nazwę, która sugeruje jej funkcję w sposób, który może zaspokoić najbardziej imperatywny programista i najbardziej funkcjonalny z ... cóż, miejmy nadzieję, że każdy może narzekać na to samo:inject .

Weź wartość. „Wstrzyknąć” go do Functor, Applicative, Monad, lub co-ma-ty. Głosuję na „ inject” i zaakceptowałem tę wiadomość.

BMeph
źródło
4
Zwykle skłaniam się ku czymś w rodzaju „jednostka” lub „winda”, ale w Haskell to już zbyt wiele innych znaczeń. injectto doskonała nazwa i prawdopodobnie lepsza od mojej, chociaż na marginesie, słowo „inject” jest używane w - myślę - Smalltalk i Ruby dla jakiejś metody składania w lewo. Jednak nigdy nie rozumiałem tego wyboru imienia ...
CA McCann,
3
To jest bardzo stary wątek, ale myślę, że injectw Ruby i Smalltalk jest używany, ponieważ jest to tak, jakbyś "wstrzykiwał" operator między każdy element na liście. Przynajmniej tak zawsze o tym myślałem.
Jonathan Sterling
1
Aby ponownie podnieść ten stary wątek boczny: nie wstrzykujesz operatorów, zastępujesz (eliminujesz) konstruktory, które już istnieją. (Patrząc na odwrót, wstrzykujesz stare dane do nowego typu). W przypadku list eliminacja jest po prostu foldr. (Zamieniasz (:)i [], gdzie (:)przyjmuje 2 argumenty i []jest stałą, stąd foldr (+) 0 (1:2:3:[])1+2+3+0.) Po Boolprostu if- then- else(dwie stałe, wybierz jedną) i Maybenazywa się maybe… Haskell nie ma jednej nazwy / funkcji do tego, ponieważ wszystkie mają różne typy (generalnie elim to po prostu rekurencja / indukcja)
nikt
@CAMcCann Smalltalk otrzymało tę nazwę od piosenki Arlo Guthrie o projekcie na wojnę w Wietnamie, w której zbierano, selekcjonowano, czasem odrzucano i wstrzykiwano nieszczęsnych młodych mężczyzn.
Tom Anderson,
7

W skrócie:

  • <*>możesz to nazwać stosowaniem . Więc Maybe f <*> Maybe amoże być wymawiane jako Apply Maybe foverMaybe a .

  • Możesz zmienić nazwę purena of, tak jak robi to wiele bibliotek JavaScript. W JS można utworzyć Maybez Maybe.of(a).

Również wiki Haskell'a ma stronę na wymowie operatorów językowych tutaj

Marcelo Lazaroni
źródło
3
(<*>) -- Tie Fighter
(*>)  -- Right Tie
(<*)  -- Left Tie
pure  -- also called "return"

Źródło: Haskell Programming from First Principles , autor: Chris Allen i Julie Moronuki

dmvianna
źródło
O ile wiem, te nazwy nie przyjęły się do końca.
dfeuer
@dfeuer czeka na następne pokolenie Haskellerów, którzy używają tej książki jako podstawowego materiału do nauki.
dmvianna
1
To może się zdarzyć. Nazwy są jednak okropne, ponieważ nie mają związku ze znaczeniami.
dfeuer
1
@dfeuer, nigdzie nie widzę dobrego rozwiązania. „ap” / „apply” jest równie niejasne jak „tie fighter”. Wszystko jest zastosowaniem funkcji. Jednak nazwa pojawiająca się niespodziewanie może nabrać znaczenia poprzez użycie. Dobrym przykładem jest „Apple”. Nawiasem mówiąc, zwrot monady jest czysty aplikacyjnie. Żadnych wynalazków.
dmvianna