2014-06-30 13 views
5

Uczę się Haskella z przewodnikiem Learn You a Haskell i utknąłem na przykładzie sekwencjonowania aplikacyjnego na liście funkcji. Chapter 11: Functors, Applicative Functors and Monoids definiuje sequenceA być:Jak działa sekwencjonowanie aplikacyjne na liście funkcji?

sequenceA :: (Applicative f) => [f a] -> f [a] 
sequenceA [] = pure [] 
sequenceA (x:xs) = (:) <$> x <*> sequenceA xs 

Jestem trochę zawstydzony tym przykładem wykorzystania sequenceA:

> sequenceA [(+3),(+2),(+1)] 3 
[6,5,4] 

mam rozszerzoną aplikację ręcznie o ile mogę do tego, co uważam, że jest popraw:

(:) <$> (+3) <*> sequenceA [(+2), (+1)] 
(:) <$> (+3) <*> (:) <$> (+2) <*> (:) <$> (+1) <*> sequenceA [] 
(:) <$> (+3) <*> (:) <$> (+2) <*> (:) <$> (+1) <*> pure [] 
(:) <$> (+3) <*> (:) <$> (+2) <*> (:) <$> (+1) <*> const [] 

Co nie widzę jednak to, w jaki sposób drugi argument do pierwotnego zastosowania sequenceA, 3 zostanie zastosowany do każdego z częściowo applie d funkcje podane na liście. Byłbym wdzięczny za jakąś pomoc w próbie oblania głowy tym, co dzieje się w ocenie tego oświadczenia.

+4

wskazówka: aplikacyjnych instancja jest '(->) A'. Wypracuj aplikację 'fmap' /' <*> '! – Xeo

Odpowiedz

10

Kiedy masz do czynienia z typami generycznymi trzeba najpierw ustalić, co typem betonu jest. W tym przypadku mamy

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

stosowane do

[(+3),(+2),(+1)] :: Num a => [a -> a] 

Innymi słowy, chociaż to trochę dziwne, aby zobaczyć w pierwszym, f staje a -> (poprawnie napisane (->) a), a więc rodzaj sequenceA wyspecjalizowany, to:

sequenceA :: Num a => [a -> a] -> a -> [a] 

Co powinno już wyjaśnić, skąd pochodzi ten dodatkowy argument.


Jak działa sequenceA? Aby to zrozumieć, musimy zrozumieć instancję Applicative dla (->) a. W szczególności

instance Functor ((->) a) where 
    fmap f g = f . g 

instance Applicative ((->) a) where 
    pure a' = \a -> a' 
    ff <*> fx = \a -> (ff a) (fx a) 

gdzie widzimy, że (<*>) spełnia dwie funkcje i tworzy trzecią. Argument z tej trzeciej funkcji jest stosowany do każdej z funkcji wejściowych (tutaj, ff i fx), a następnie ich wyniki są stosowane względem siebie.

Tak więc zastosowanie w monadzie (->) a oznacza, że ​​ostateczny argument a dla wyniku jest dystrybuowany do wszystkich funkcji składowych.

I to jest to, co widzimy z sequenceA

sequenceA [(+3),(+2),(+1)] 
== 
\a -> [(a + 3), (a + 2), (a + 1)] 
+0

Dziękuję za bardzo pomocne wyjaśnienie, J! :) Wierzę, że mam coś po zabawie z ghci przez chwilę: '> ((:). (+3) 3 $) (((:) (+2) 3 $) (((:) (+1) 3 $) (const [] 3))) ' ' [6, 5, 5] ' Chociaż muszę przyznać, nadal nie w pełni rozumiem, w jaki sposób 3 jest dystrybuowane. Widzę, że operator fmap infiks pełni funkcję 3, ale trudno mi sobie wyobrazić, że rzeczywiście jest wysyłany do wszystkich funkcji. Może jest lepszy sposób na wyjaśnienie tego, a jeśli nie, to mam nadzieję, że zrozumienie, które przychodzi z większą praktyką. – bpaterni

+0

Być może przydałoby się wtedy dalsze badanie "sekwencji A". Spróbuj spojrzeć na to w innych aplikacjach lub monadach ("Czytnik" będzie ważny!). Mówiąc prościej, 'sequenceA' ma kilka skutecznych obliczeń i" dzieli się "ich efektami w sekwencji. Tak więc imię. Dla sekwencji aplikacji funkcji nie ma znaczenia, ale udostępnianie ma. –

1

Zastosowanie:

instance Applicative ((->) a) where 
    pure = const 
    (<*>) f g x = f x (g x) 

możemy czerpać:

 (:) <$> (+3) <*> (  (:) <$> (+2) <*> ( (:) <$> (+1) <*> const [])) 
pure (:) <*> (+3) <*> (pure (:) <*> (+2) <*> (pure (:) <*> (+1) <*> const [])) 
const (:) <*> (+3) <*> (const (:) <*> (+2) <*> (const (:) <*> (+1) <*> const [])) 
(const (:) <*> (+3)) <*> ((const (:) <*> (+2)) <*> ((const (:) <*> (+1)) <*> const [])) 
(const (:) <*> (+3)) <*> ((const (:) <*> (+2)) <*> ((\x -> (const (:)) x (x+1)) <*> const [])) 
(const (:) <*> (+3)) <*> ((const (:) <*> (+2)) <*> ((\x -> (:) (x+1)) <*> const [])) 
(const (:) <*> (+3)) <*> ((const (:) <*> (+2)) <*> (\y -> (\x -> (:) (x+1)) y (const [] y))) 
(const (:) <*> (+3)) <*> ((const (:) <*> (+2)) <*> (\y -> (\x -> (:) (x+1)) y [])) 
(const (:) <*> (+3)) <*> ((const (:) <*> (+2)) <*> (\y -> ((y+1):) [])) 
(const (:) <*> (+3)) <*> ((const (:) <*> (+2)) <*> (\y -> [y+1])) 
(const (:) <*> (+3)) <*> (((\x -> (const (:)) x (x+2)) <*> (\y -> [y+1])) 

(const (:) <*> (+3)) <*> (\z -> ((\x -> (const (:)) x (x+2)) z ((\y -> [y+1])z)) 
(const (:) <*> (+3)) <*> (\z -> (((const (:)) z (z+2)) ([z+1])) 
(const (:) <*> (+3)) <*> (\z -> ((z+2):) [z+1]) 
(const (:) <*> (+3)) <*> (\z -> [z+2,z+1]) 
(\x -> (const (:)) x (x+3)) <*> (\z -> [z+2,z+1]) 
(\x -> const (:) (x+3)) <*> (\z -> [z+2,z+1]) 
(\x -> ((x+3):)) <*> (\z -> [z+2,z+1]) 
\w -> (\x -> ((x+3):)) w ((\z -> [z+2,z+1]) w) 
\w -> ((w+3):) [w+2,w+1] 
\w -> [w+3,w+2,w+1] 

Założę się, że zawiedli trochę nawiasach

Powiązane problemy