Jak się nazywa <*> i co robi? [zamknięte]
Jak działają te funkcje w typeklasie aplikacji?
(<*>) :: 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ę pure
na coś bardziej przyjaznego nie-matematykom, jak byś to nazwał?
4 answers
Przepraszam, nie znam się na matematyce, więc jestem ciekaw, jak wymawiać funkcje w typeklasie aplikacyjnym [83]}
Znajomość matematyki, czy nie, jest tu w dużej mierze nieistotna, myślę. Jak zapewne wiesz, Haskell zapożycza kilka fragmentów terminologii z różnych dziedzin abstrakcyjnej matematyki, przede wszystkim teoria kategorii , skąd otrzymujemy funktory i monady. Użycie tych terminów w Haskell odbiega nieco od formalnych definicji matematycznych, ale zwykle są na tyle blisko, że i tak są dobrymi określeniami opisowymi.
Klasa typu Applicative
znajduje się gdzieś pomiędzy Functor
a Monad
, więc można oczekiwać, że będzie miała podobną matematyczną podstawę. Dokumentacja modułu Control.Applicative
zaczyna się od:
Ten moduł opisuje strukturę pośrednią między funktorem a monadą: dostarcza czystych wyrażeń i sekwencjonowania, ale nie wiąże. (Technicznie, silny monoidalny funkcjonariusz.)
Hmm.
class (Functor f) => StrongLaxMonoidalFunctor f where
. . .
Nie tak chwytliwe jak Monad
, myślę.
Wszystko to sprowadza się do tego, że Applicative
nie odpowiada żadnemu pojęciu, które jest szczególnie interesujące {101]} matematycznie, więc nie ma gotowych terminów, które odzwierciedlają sposób, w jaki są używane w Haskell. Więc odstaw matematykę na bok.
Jeśli chcemy wiedzieć, jak się nazywać {[18] } może pomóc wiedzieć, co to w zasadzie znaczy.
Więc o co chodzi z Applicative
i dlaczego robimy tak to nazywamy?
Co Applicative
jest w praktyce sposobem na podniesienie arbitralnych funkcji do Functor
. Rozważmy kombinację Maybe
(prawdopodobnie najprostszy nietrywialny Functor
) i Bool
(podobnie najprostszy nietrywialny typ danych).
maybeNot :: Maybe Bool -> Maybe Bool
maybeNot = fmap not
Funkcja fmap
pozwala nam podnieść not
z pracy nad Bool
do pracy nad Maybe Bool
. Ale co jeśli chcemy podnieść (&&)
?
maybeAnd' :: Maybe Bool -> Maybe (Bool -> Bool)
maybeAnd' = fmap (&&)
Cóż, to wcale nie tego chcemy! Właściwie, to jest całkiem bezużyteczne. Możemy spróbować być sprytni i przemycić kolejny Bool
do Maybe
przez plecy...
maybeAnd'' :: Maybe Bool -> Bool -> Maybe Bool
maybeAnd'' x y = fmap ($ y) (fmap (&&) x)
...ale to nie jest dobre. Po pierwsze, to jest złe. Jeszcze jedno, to jest brzydkie . Możemy próbować dalej, ale okazuje się, że nie ma sposobu, aby podnieść funkcję wielu argumentów do pracy na arbitralnej Functor
. Irytujące!
Z drugiej strony, możemy to zrobić łatwo, jeśli użyjemy Maybe
' s Monad
instancja:
maybeAnd :: Maybe Bool -> Maybe Bool -> Maybe Bool
maybeAnd x y = do x' <- x
y' <- y
return (x' && y')
Tłumaczenie prostej funkcji jest kłopotliwe, dlatego Control.Monad
zapewnia funkcję, która robi to automatycznie, {36]}. 2 w swojej nazwie odnosi się do faktu, że działa na funkcjach dokładnie dwóch argumentów; podobne funkcje istnieją dla funkcji 3, 4 i 5 argumentów. Funkcje te są lepsze , ale nie doskonałe, a podanie liczby argumentów jest brzydkie i niezdarne.
Co prowadzi nas do papieru, który wprowadzono klasę typu aplikacyjnego . W nim autorzy poczynili zasadniczo dwie uwagi: [83]}
- przeniesienie funkcji wielowątkowych do
Functor
jest bardzo naturalną rzeczą do zrobienia
To nie wymaga pełnych możliwości]}
Aplikacja funkcji normalnej jest zapisywana za pomocą prostego zestawienia terminów, więc aby "aplikacja podnoszona" była jak najprostsza i naturalna, w artykule wprowadzono operatory infiksowe do wniosek, podnoszony do Functor
, i klasy typu, aby zapewnić to, co jest potrzebne do tego.
Wszystko sprowadza nas do następującego punktu: (<*>)
po prostu reprezentuje aplikację funkcyjną-więc po co ją wymawiać inaczej niż robi się to za pomocą "operatora zestawiania"?
Ale jeśli nie jest to zbyt satysfakcjonujące, możemy zauważyć, że moduł Control.Monad
zapewnia również funkcję, która robi to samo dla monad: {83]}
ap :: (Monad m) => m (a -> b) -> m a -> m b
Gdzie ap
jest, z oczywiście, skrót od "apply". Ponieważ dowolny Monad
Może być Applicative
, a ap
potrzebuje tylko podzbioru funkcji obecnych w tym ostatnim, możemy być może powiedzieć, że gdyby (<*>)
nie był operatorem, powinien być nazywany ap
.
Możemy również podchodzić do rzeczy z innej strony. Operacja liftingu Functor
nazywa się fmap
, ponieważ jest uogólnieniem operacji map
na listach. Jaka funkcja na listach będzie działać (<*>)
? Jest to, co ap
robi na listach, oczywiście, ale samo to nie jest 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 idei równoległego ustawiania list, stosując każdą funkcję w pierwszej do odpowiadającego jej elementu drugiej. Niestety dla naszego starego przyjaciela Monad
, ta prosta operacja narusza prawa monad jeśli listy są różnej długości. Ale to sprawia, że grzywna Applicative
, w którym to przypadku (<*>)
staje się sposobem naciągania uogólnionej wersji zipWith
, więc może wyobrażamy sobie nazywanie jej fzipWith
?
Ten pomysł zapinania zapięcia zapina nas w koło. Pamiętasz te matematyczne rzeczy o funkcjach monoidalnych? Jak sama nazwa wskazuje, są to sposoby łączenia struktury monoidów i funktorów, z których oba są znane typu Haskell klasy:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Monoid a where
mempty :: a
mappend :: a -> a -> a
Jak by to wyglądało, gdybyś włożył je do pudełka razem i potrząsnął nim trochę? Z Functor
zachowamy ideę struktury niezależną od jej parametru typu, a z Monoid
zachowamy ogólną formę 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" Functor
i nie możemy wyczarować wartości dowolnego typu, więc naprawimy Typ mfEmpty
jako f ()
.
My też nie chcemy aby wymusić mfAppend
potrzebę 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 dla mfAppend
? Mamy dwa dowolne typy, o których nic nie wiemy, więc nie mamy wielu opcji. Najrozsądniej jest po prostu zachować oba:
class (Functor f) => MonoidalFunctor f where
mfEmpty :: f ()
mfAppend :: f a -> f b -> f (a, b)
W tym momencie mfAppend
jest teraz wyraźnie uogólnioną wersją zip
na listach i możemy łatwo zrekonstruowaćApplicative
: {]}
mfPure x = fmap (\() -> x) mfEmpty
mfApply f x = fmap (\(f, x) -> f x) (mfAppend f x)
To również pokazuje nam, że pure
jest związane z elementem tożsamości Monoid
, więc innymi dobrymi nazwami może być cokolwiek, co sugeruje Wartość jednostki, operację null lub coś takiego.
To było długie, więc podsumowując:
(<*>)
jest tylko zmodyfikowaną aplikacją funkcyjną, więc możesz albo odczytać ją jako " ap " lub "apply", albo ominąć ją całkowicie tak, jak normalnie działa aplikacja.(<*>)
również z grubsza uogólniazipWith
na listy, więc można go odczytać jako "funkcje zip z", podobnie jak czytaniefmap
jako " mapa funkcjonariusz z".
Pierwsza jest bliższa intencji Applicative
klasy typu-jak sama nazwa wskazuje-więc to polecam.
-
(<$>)
, który podnosi funkcję jednowątkową doFunctor
-
(<*>)
, który łączy funkcję wielowątkową za pomocąApplicative
-
(=<<)
, która wiąże funkcję, która zapisujeMonad
na istniejące obliczenia
Wszystkie trzy są, w sercu, po prostu regularne stosowanie Funkcji, trochę doprawione.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-07-14 01:55:47
Ponieważ nie mam ambicji poprawy na odpowiedź techniczna C. A. McCann, zajmę się tym bardziej puszystym:
Gdybyś mógł zmienić nazwę
pure
na coś bardziej przyjaznego dla podunksów jak ja, jak byś to nazwał?
Jako alternatywę, tym bardziej, że nie ma końca ciągła, pełna gniewu i zdrady Wersja Monad
, zwana " return
", proponuję inną nazwę, która sugeruje jej funkcję w sposób, który może zaspokoić najbardziej imperatyw programistów imperatywnych, a najbardziej funkcjonalny z..cóż, mam nadzieję, że każdy może narzekać na to samo: inject
.
Weź wartość. "Inject" it into the Functor
, Applicative
, Monad
, albo co-masz-Ciebie. Głosuję na "inject
" i zatwierdziłem tę wiadomość.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:33
W skrócie:
<*>
można go nazwać apply . Więc {[1] } można wymawiać jako ZastosujMaybe f
nadMaybe a
.Możesz zmienić nazwę
pure
naof
, tak jak robi to wiele bibliotek JavaScript. W JS możesz utworzyćMaybe
zMaybe.of(a)
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-08 10:39:57
(<*>) -- Tie Fighter
(*>) -- Right Tie
(<*) -- Left Tie
pure -- also called "return"
Źródło: Programowanie Haskella z pierwszych zasad , autorstwa Chrisa Allena i Julie Moronuki
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-31 02:14:13