2016-02-14 17 views
8

Jestem nowicjuszem w zakresie programowania funkcjonalnego (pochodzącego z javascript) i ciężko mi jest odróżnić te dwie rzeczy, co jest również męczące z moim rozumieniem funktorów kontra monad.Różnica w zdolnościach między fmap i bind?

funktora:

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

Monada (uproszczonych)

class Monad m where 
    (>>=) :: m a -> (a -> m b) -> m b 
  • fmap wykonuje funkcję i funktora i zwraca funktor.
  • >>= przyjmuje funkcję i monadę i zwraca monadę.

Różnica między nimi polega na parametr funkcyjny:

  • fmap - (a -> b)
  • >>= - (a -> m b)

>>= przyjmuje parametr funkcyjny zwraca monadę. Wiem, że to jest znaczące, ale mam trudności z zobaczeniem, jak ta jedna drobna rzecz sprawia, że ​​monady są znacznie potężniejsze niż funktory. Czy ktoś może wyjaśnić?

+4

jest to łatwiejsze w przypadku odwróconej wersji '(>> =)', ['(= <<)'] (https://stackoverflow.com/questions/34545818/is-monad-bind-operator- bliżej funkcji-kompozycji-łączenia-lub-functi/34561605 # 34561605). Z '(g <$>) :: f a -> f b', funkcja' g :: a -> b' nie ma wpływu na owijanie 'f'" - nie zmienia tego. Z '(k = <<) :: m a -> mb', funkcja' k :: a -> mb' sama * tworzy * nowe "m" owijanie ", więc może się zmienić –

+0

@WillNess Mogę" zrozumieć "to, ale ja Nie widzę tego. Myślę, że prawdziwym problemem, jaki mam, jest to, że nie widzę tego, co '>> =' może zrobić, czego 'fmap' nie może zrobić, w mojej głowie są one równoważne, ponieważ nie widziałem przykład, który pokazuje, że fmap jest niewystarczająca, – m0meni

+4

przechodzenie z listami, spróbuj odfiltrować niektóre elementy z listy, używając 'map' .nie możesz, ale z' concatMap', możesz: 'mapować (\ x- > x + 1) [1,2,3] 'vs' concatMap (\ x-> [x, x + 1 | nawet x]) [1,2,3]) '. –

Odpowiedz

13

No (<$>) jest aliasem dla fmap i jest taka sama jak (>>=) z argumentami zamienionych:

(<$>) :: (x -> y) -> b x -> b y 
(=<<) :: (x -> b y) -> b x -> b y 

Różnica jest dość oczywiste: z funkcją wiązania, możemy zastosować funkcję zwracającą a b y zamiast y. Więc co to za różnica?

Rozważmy ten mały przykład:

foo <$> Just 3 

Uwaga, (<$>) będzie stosowana foo do 3 i umieścić wynik z powrotem do Just. Innymi słowy, wynik tego obliczenia nie może być być . Wręcz przeciwnie:

bar =<< Just 3 

To obliczenie może powrócić Nothing. (Na przykład, bar x = Nothing to zrobi.)

Możemy zrobić coś podobnego z listy monady:

foo <$> [Red, Yellow, Blue] -- Result is guaranteed to be a 3-element list. 
bar =<< [Red, Yellow, Blue] -- Result can be ANY size. 

w skrócie, z (<$>) (tj fmap), „struktury” w wyniku jest zawsze identyczne z danymi wejściowymi.Ale z (tj. (>>=)) struktura wyniku może się zmienić. Pozwala to na warunkowe wykonywanie, reagowanie na dane wejściowe i całą masę innych rzeczy.

+4

tylko dla kompletności, Wnioskodawca może zwrócić 'Nic' też:' Nic <*> Tylko 3'. Różnica polega na tym, że "rurociąg" (tj. Struktura obliczeniowa) jest * ustalony *, gdy obliczenia są złożone, * przed * it * "działa" *. Ale w przypadku Monad, rurociąg może się zmieniać w zależności od wytwarzanych wartości *, podczas gdy * on "działa". (w przypadku IO, "3" jest prawdopodobnie odbierane np. jako dane wejściowe użytkownika). - Przykładem * list * jest esp. dobrze tutaj: '(foo <$>)' zachowuje strukturę (długość listy); '([baz, quux] <*>)' zmieni strukturę * przewidywalnie * (utwórz długość-6 ​​list); z Monadem wszystkie zakłady są wyłączone. –

8

Krótka odpowiedź brzmi: jeśli możesz zmienić m (m a) w m a w sensie, który ma sens, to jest Monada. Jest to możliwe dla wszystkich Monad, ale niekoniecznie dla Funktorów.

Myślę, że najbardziej mylące jest to, że wszystkie popularne przykłady Funktorów (np. List, Maybe, IO) to także Monady. Potrzebujemy przykładu czegoś, co jest Funktorem, ale nie Monadą.

Użyję przykładu z hipotetycznego programu kalendarza. Poniższy kod definiuje funkcję Event Functor, która przechowuje dane związane ze zdarzeniem i czasem jego wystąpienia.

import Data.Time.LocalTime 

data Event a = MkEvent LocalTime a 

instance Functor Event where 
    fmap f (MkEvent time a) = MkEvent time (f a) 

W Event obiektów zapamiętuje czas, który występuje zdarzenie i jakieś dodatkowe informacje, które mogą być zmienione za pomocą fmap. Teraz spróbujmy i sprawiają, że monady:

instance Monad Event where 
    (>>=) (MkEvent timeA a) f = let (MkEvent timeB b) = f a in 
           MkEvent <notSureWhatToPutHere> b 

stwierdzamy, że nie możemy, bo skończy się z dwóch LocalTime obiektów. timeA z podanego i timeB z Event podanego przez wynik f a. Nasz typ Event jest zdefiniowany jako mający tylko jeden LocalTime (time), w którym występuje i dlatego uczynienie z niego Monady nie jest możliwe bez zamieniania dwóch na LocalTime s w jeden. (Może się zdarzyć, że może to mieć sens, więc jeśli naprawdę chcesz, możesz zamienić to w monadę).

+3

Jednym z przykładów klasycznego/zwykłego funktora, który nie jest monadą, jest 'nowy typ Const a b = Const a'. – dfeuer

+3

'pure x >> = f' jest wymagane przez prawo monady, aby było' fx', ale 'pure :: b -> Const a b' nie może używać swojego argumentu. – dfeuer

+1

@dfeuer To szwy [zbyt proste, aby być proste] (https://ncatlab.org/nlab/show/too+simple+to+be+beimple). Ponadto nie mogę znaleźć sposobu na napisanie instancji Funktora poza 'fmap f (Const x) = Const x' – HEGX64

3

Załóżmy na chwilę, że IO były tylko Functor, a nie Monad. Jak możemy uporządkować dwie akcje? Powiedz, tak jak getChar :: IO Char i putChar :: Char -> IO().

Możemy spróbować mapować ponad getChar (działanie, które po uruchomieniu odczytuje Char ze standardowego wejścia) przy użyciu putChar.

fmap putChar getChar :: IO (IO()) 

Teraz mamy program, który po uruchomieniu, odczytuje Char ze standardowego wejścia i tworzy program, który po uruchomieniu, pisze Char na standardowe wyjście. Ale naprawdę chcemy programu, który po uruchomieniu czyta Char ze stdin i zapisuje Char na standardowe wyjście. Więc potrzebujemy „spłaszczenie” (w przypadku IO „sekwencjonowanie”) z funkcji typu:

join :: IO (IO()) -> IO() 

Functor sama nie zapewnia tę funkcję. Ale to jest funkcją Monad, gdzie to ma bardziej ogólny typ:

join :: Monad m => m (m a) -> m a 

Co to wszystko ma wspólnego z >>=? Jak to się dzieje, monadycznego wiążą się właśnie kombinacja fmap i join:

:t \m f -> join (fmap f m) 
(Monad m) => m a1 -> (a1 -> m a) -> m a 

Innym sposobem widząc różnicy jest to, że fmap nigdy się nie zmienia ogólnej struktury odwzorowanym wartości, ale join (a więc >>= również) mogę to zrobić.

Pod względem IO działań fmap będzie nigdy przyczyną Ważne odczytuje/zapisuje lub inne efekty. Ale join sekwencyjnie odczytuje/zapisuje wewnętrzną akcję po akcji zewnętrznej.

Powiązane problemy