2015-09-01 30 views
11

join jest zdefiniowany wraz z bind w celu spłaszczenia połączonej struktury danych w jedną strukturę.Czy istnieje jakaś intuicja do zrozumienia łączenia dwóch funkcji w Monadzie?

Od typ widoku systemowym, (+) 7 :: Num a => a -> a można uznać jako Functor, (+) :: Num a => a -> a -> a można uznać jako Functor z Functor, jak się trochę intuicji o tym, a nie tylko polegać na systemie typu? Dlaczego join (+) 7 === 14?

Mimo że możliwe jest uzyskanie końcowego wyniku poprzez ręczne przejście procesu wiązania funkcji, byłoby wspaniale, gdyby pewne intuicje zostały podane.

To jest z ćwiczeń NICTA.

-- | Binds a function on the reader ((->) t). 
-- 
-- >>> ((*) =<< (+10)) 7 
-- 119 
instance Bind ((->) t) where 
    (=<<) :: 
    (a -> ((->) t b)) 
    -> ((->) t a) 
    -> ((->) t b) 
    (f =<< a) t = 
    f (a t) t 

-- | Flattens a combined structure to a single structure. 
-- 
-- >>> join (+) 7 
-- 14 
join :: 
    Bind f => 
    f (f a) 
    -> f a 
join f = 
    id =<< f 

*Course.State> :t join (+) 
join (+) :: Num a => a -> a 
*Course.State> :t join 
join :: Bind f => f (f a) -> f a 
*Course.State> :t (+) 
(+) :: Num a => a -> a -> a 
+7

"' (+) 7 :: Num a => a -> a' może być uważany za "Funktora" ... "proszę nie mówić takich rzeczy. '(7+)' nie jest funktorem, to tak, jakby powiedzieć, że atom węgla to diament. Funktorem jest '(Int ->)', tzn. Konstruktor typów, który przyjmuje typy i generuje funkcje z liczb do tego typu. – leftaroundabout

+0

Dla funkcji, 'join f = \ a -> f a a'. Zatem 'join (+) = \ a -> a + a'. – AJFarmar

+0

@leftaroundabout Czy nie jest '(+) 7 :: Num a => a -> a' functor w kategorii' Num'? – Kamel

Odpowiedz

9

jak uzyskać intuicję, zamiast polegać tylko na systemie typu?

Powiedziałbym raczej, że poleganie na systemie typów jest świetnym sposobem na zbudowanie określonego rodzaju intuicji.Rodzaj join jest:

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

specjalistyczne do (->) r, staje się:

(r -> (r -> a)) -> (r -> a) 

Teraz spróbujmy zdefiniować join dla funkcji:

-- join :: (r -> (r -> a)) -> (r -> a) 
join f = -- etc. 

Wiemy, że wynik musi być Funkcja r -> a:

join f = \x -> -- etc. 

Jednak nic nie wiemy na temat typów r i a, a zatem nie wiemy nic w szczególności o f :: r -> (r -> a) i x :: r. Nasza niewiedza oznacza, że ​​jest dosłownie tylko jedna rzecz, jaką możemy zrobić z nimi: przechodząc x jako argument, zarówno f i f x:

join f = \x -> f x x 

Dlatego join dla funkcji przechodzi ten sam argument dwa razy, ponieważ jest to jedyna możliwa realizacja. Oczywiście, że implementacja jest tylko właściwa monadycznego join ponieważ wynika prawa monady:

join . fmap join = join . join 
join . fmap return = id 
join . return = id 

Weryfikacja może być kolejny miły ćwiczeń.

7

Idąc z tradycyjnym analogię do monadzie jako kontekst dla obliczeń, join jest sposób łączenia kontekstów. Zacznijmy od twojego przykładu. join (+) 7. Używanie funkcji jako monady implikuje monadę czytnika. (+ 1) to monada czytająca, która przyjmuje otoczenie i dodaje do niej jedną. Tak więc, (+) będzie monadą czytającą w monadie czytnika. Zewnętrzna monada czytająca przyjmuje środowisko n i zwraca czytelnika o postaci (n +), która zajmie nowe środowisko. join po prostu łączy dwa środowiska, aby podać je raz i dwukrotnie stosuje dany parametr. join (+) === \x -> (+) x x.

Teraz, bardziej ogólnie, spójrzmy na kilka innych przykładów. Monada o numerze Maybe reprezentuje potencjalną awarię. Wartość Nothing jest nieudanym obliczeniem, podczas gdy Just x jest sukcesem. A Maybe w ramach Maybe jest obliczeniem, które może zakończyć się niepowodzeniem dwukrotnie. Wartość Just (Just x) jest oczywiście sukcesem, więc dołączenie do niej daje Just x. A Nothing lub wskazuje na błąd w pewnym momencie, więc dołączenie do możliwej awarii powinno oznaczać, że obliczenia nie powiodły się, tj. Nothing.

Podobna analogia może być zawarta na liście monady, dla których join jest jedynie concat, monady pisarz, który wykorzystuje monoidal operatora <> połączyć wartości wyjściowych w pytaniu, czy jakiejkolwiek innej monady.

join jest podstawową własnością monad i jest operacją, która czyni ją znacznie silniejszą niż funktor lub funktor aplikacyjny. Funktory można odwzorowywać, aplikacje mogą być sekwencjami, monady można łączyć. Kategorycznie monada jest często definiowana jako join i return. Tak się składa, że ​​w Haskell wygodniej jest zdefiniować go w kategoriach: return, (>>=) i i , ale te dwie definicje okazały się synonimami.

+0

jak to jest '+ 1' monada czytelnika? masz na myśli jakiś konceptualnie, czy też na poziomie typu? –

+0

'(+ 1)' jest funkcją, która pobiera argument i dodaje jeden. Koncepcyjnie można go postrzegać jako wartość zależną od wartości tylko do odczytu, której jeszcze nie zna. '(+ 1)' może być postrzegane jako liczba całkowita, której nie możemy sprawdzić, dopóki nie damy mu "środowiska". –

+0

, więc to tylko analogia pojęciowa, a nie fakt na poziomie typu. –

2

Intuicją na temat join jest to, że kabaczek 2 pojemniki w jednym. .e.g

join [[1]] => [1] 
join (Just (Just 1)) => 1 
join (a christmas tree decorated with small cristmas tree) => a cristmas tree 

etc ...

Teraz, w jaki sposób można dołączyć funkcje? W rzeczywistości funkcje mogą być postrzegane jako pojemnik. Jeśli spojrzysz na tabelę skrótu, na przykład. Dajesz klucz i dostajesz wartość (lub nie). Jest to funkcja key -> value (lub jeśli wolisz key -> Maybe value). Jak dołączyć do 2 HashMap?

Załóżmy, że mam (w stylu Pythona) h={"a": {"a": 1, "b": 2}, "b" : {"a" : 10, "b" : 20 }} jak mogę do niego dołączyć, czy wolisz go spłaszczyć? Po otrzymaniu "a" jakiej wartości powinienem uzyskać? h["a"] daje mi {"a":1, "b":2}. Jedyne, co mogę z tym zrobić, to znaleźć "a" ponownie w tej nowej wartości, co daje mi 1. Dlatego join h jest równy {"a":1, "b":20}.

To samo dotyczy funkcji.