Potrzeba czystego w aplikacjach

19

Uczę się Aplikacji Haskella. Wydaje mi się (prawdopodobnie się mylę), że purefunkcja nie jest tak naprawdę potrzebna, na przykład:

pure (+) <*> [1,2,3] <*> [3,4,5]

można zapisać jako

(+) <$> [1,2,3] <*> [3,4,5]

Czy ktoś może wyjaśnić zalety tej purefunkcji w porównaniu z jawnym mapowaniem fmap?

Gil Shafriri
źródło
1
Masz rację - pure f <*> xjest dokładnie taki sam jak fmap f x. Jestem pewien, że istnieje jakiś powód, dla którego purezostał włączony Applicative, ale nie jestem do końca pewien, dlaczego.
bradrn
4
Nie mam czasu na odpowiedź i nie jestem przekonany, że i tak byłby dobry lub kompletny, ale jedno spostrzeżenie: purepozwala na użycie, no cóż, „czystych” wartości w obliczeniach aplikacyjnych. Chociaż, jak poprawnie obserwować, pure f <*> xjest taka sama jak f <$> x, nie ma takiego ekwiwalent za, powiedzmy f <*> x <*> pure y <*> z. (Przynajmniej nie sądzę.)
Robin Zigmond,
3
Jako kolejne, bardziej teoretyczne uzasadnienie - istnieje alternatywne sformułowanie, które wiąże je ściśle z ważną Monoidklasą - w którym pureodpowiada Monoidelement tożsamości. (Sugeruje to, że Applicativebez puremoże być interesujące, ponieważ Semigroup- które Monoidniekoniecznie musi mieć tożsamość - wciąż jest używane. Właściwie, teraz myślę o tym, wydaje mi się, że PureScript ma dokładnie taką pureklasę „Aplikacja bez ”, chociaż nie nie wiem, do czego służy).
Robin Zigmond,
2
@RobinZigmond fmap (\f' x' z' -> f' x' y z') f <*> x <*> z, tak myślę. Pomysł tkwi w Applicativedokumentacji jako prawo „wymiany”.
HTNW
3
@RobinZigmond Applicativenie pureistnieje jak Applyz półgrup .
zdublowany

Odpowiedzi:

8

Jestem tutaj na granicy moich kompetencji, więc nie bierz tego dłużej niż jest, ale komentarz był trochę za długi.

Mogą istnieć praktyczne powody, aby dołączyć puredo klasy typu, ale wiele abstrakcji Haskella pochodzi z podstaw teoretycznych i uważam, że Applicativetak też jest. Jak mówi dokumentacja, jest to silny, luźny funktor monoidalny (więcej szczegółów na stronie https://cstheory.stackexchange.com/q/12412/56098 ). Przypuszczam, że puresłuży ona jako tożsamość , tak jak returnrobi to dla Monad(który jest monoidem w kategorii endofunkcji ).

Rozważ purei liftA2:

pure :: a -> f a
liftA2 :: (a -> b -> c) -> f a -> f b -> f c

Jeśli trochę zmrużysz oczy, możesz sobie wyobrazić, że liftA2jest to operacja binarna, a dokumentacja tego również stwierdza:

Podnieś funkcję binarną do akcji.

pureoznacza zatem odpowiednią tożsamość.

Mark Seemann
źródło
6
Dokładnie. Applicativebez purebyłby funktor półgrupowy zamiast monoidalnego.
lewo około
20

fmapnie zawsze to wycina. W szczególności pureto, co pozwala ci przedstawić f(gdzie fjest Applicative), kiedy jeszcze go nie masz. Dobrym przykładem jest

sequence :: Applicative f => [f a] -> f [a]

Pobiera listę „akcji” generujących wartości i zamienia ją w akcję tworzącą listę wartości. Co się stanie, gdy na liście nie będzie żadnych działań? Jedynym rozsądnym rezultatem jest akcja, która nie generuje żadnych wartości:

sequence [] = pure [] -- no way to express this with an fmap
-- for completeness
sequence ((:) x xs) = (:) <$> x <*> sequence xs

Jeśli tego nie zrobiłeś pure, będziesz zmuszony wymagać niepustej listy działań. Na pewno możesz sprawić, że zadziała, ale to tak, jakbyś mówił o dodawaniu bez wspominania o 0 lub mnożeniu bez 1 (jak mówili inni, ponieważ Applicativesą monoidalne). Będziesz wielokrotnie spotykać się z przypadkowymi przypadkami, które można łatwo rozwiązać, pureale zamiast tego musisz rozwiązać dziwne ograniczenia na wejściach i innych pomocnikach.

HTNW
źródło