Dlaczego mamy mapę, fmap i liftM?

102
map :: (a -> b) -> [a] -> [b]

fmap :: Functor f => (a -> b) -> f a -> f b

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

Dlaczego mamy trzy różne funkcje, które zasadniczo robią to samo?

fredoverflow
źródło
32
Głównie historia. fmap różni się od mapy ze względów pedagogicznych, liftM różni się od fmap z powodów historycznych (mianowicie Functor nie jest superklasą Monady)
luqui
12
Aha, i tylko po to, by mieć jasność: „zasadniczo” nie robią tego samego. Oba mapi na liftMpewno powinny robić dokładnie to samo, co fmap.
CA McCann
2
Chociaż fmapi liftMrobią dokładnie to samo, mapoczywiście jest to tylko ich szczególny przypadek, tj. Coś innego. fmap id getLinejest dobrze napisany, a map id getLinenie jest.
Thorsten

Odpowiedzi:

91

mapistnieje, aby uprościć operacje na listach i ze względów historycznych (zobacz Jaki jest sens mapy w Haskell, kiedy jest fmap? ).

Możesz zapytać, dlaczego potrzebujemy oddzielnej funkcji mapy. Dlaczego po prostu nie zrezygnować z obecnej funkcji mapowania tylko jako listy i zamiast tego zmienić nazwę fmap na map? Cóż, to dobre pytanie. Zwykłym argumentem jest to, że ktoś, kto dopiero uczy się Haskella, kiedy używa mapy nieprawidłowo, wolałby raczej zobaczyć błąd dotyczący list niż Functorów.

- Typeklasopedia , strona 20

fmapi liftMistnieją, ponieważ monady nie były automatycznie funktorami w Haskellu:

Fakt, że mamy zarówno fmap, jak i liftM, jest niefortunną konsekwencją faktu, że klasa typu Monad nie wymaga instancji Functor, mimo że matematycznie każda monada jest funktorem. Jednak fmap i liftM są zasadniczo wymienne, ponieważ błędem (raczej w sensie społecznym niż technicznym) jest bycie instancją Monady bez bycia instancją Functora.

- Typeklasopedia , strona 33

Edycja: historia agustussa mapi fmap:

Tak się nie dzieje. Stało się tak, że typ mapy został uogólniony, aby objąć Functor w Haskell 1.3. To znaczy, w Haskell 1.3 fmap nazywał się map. Ta zmiana została następnie cofnięta w Haskell 1.4 i wprowadzono fmap. Powód tej zmiany był pedagogiczny; podczas nauczania Haskella początkującym bardzo ogólny typ mapy utrudniał zrozumienie komunikatów o błędach. Moim zdaniem nie był to właściwy sposób rozwiązania problemu.

- Jaki jest cel mapy w Haskell, kiedy jest fmap?

li.davidm
źródło
14
I z mojej perspektywy jako kogoś, kto pierwszy raz zetknął się z Haskellem ponad dekadę po dokonaniu zmiany opisanej przez @augustss i spędził dużo czasu pomagając ludziom, którzy teraz uczą się tego języka, nie jest wcale jasne, czy to nawet pomogło tak czy siak. Z pewnością nie wystarczy, aby zrównoważyć bezużyteczną redundancję (która sama prowadzi do zadawania takich pytań jak to); Functorklasa jest zbyt powszechne ignorowanie, a początkujący są często mylone przez komunikaty o błędach w każdym razie!
CA McCann
11
Nie możemy po prostu usunąć liftM? Niech kod się zepsuje, kogo to obchodzi, naprawienie kodu na githubie i przesłanie go na hakowanie zajmuje zwykle mniej niż 2 dni. A może jestem dziki i szalony?
Tarrasch
1
@Tarrasch: nie wszyscy używają github, nie wszystkie pakiety mają świetne wyniki w zakresie aktualizacji na czas, a ja na przykład używam raczej liftMw trybie do-block, niż fmapdlatego, że lepiej pasuje do tego, kiedy używam liftM2, itp. także.
Ivanm
1
@ L01man ludzi pracował nad tym; zobacz stackoverflow.com/questions/5730270/ ... i przynajmniej dla klas numerycznych istnieją alternatywy: hackage.haskell.org/packages/archive/numeric-prelude/0.3.0.2/…
li.davidm
1
@ L01man Tak, to wkrótce zostanie naprawione. Aplikacyjnych Monada Wniosek (AMP) wygląda to przejdzie do następnej wersji Haskell. GHC 7.8.3 ma nową flagę, --fwarn-ampktóra pomaga zaktualizować istniejący kod przejścia.
recursion.ninja