2014-09-26 30 views
10

Jak napisać funkcję, która pobiera krotkę funkcji typu ai -> b -> ai i zwraca funkcję, która zajmuje krotkę elementów typu ai, jeden element typu b, i łączy w sobie każdy elementy do nowej krotki ai:Wielokrotne fałdy w jednym przebiegu przy użyciu ogólnej funkcji krotki

to jest podpis powinien być jak

f :: (a1 -> b -> a1, a2 -> b -> a2, ... , an -> b -> an) -> 
    (a1, a2, ... , an) -> 
    b -> 
    (a1, a2, ... , an) 

taka, że:

f (min, max, (+), (*)) (1,2,3,4) 5 = (1, 5, 8, 20) 

Punkt ten jest więc mogę napisać:

foldlMult' t = foldl' (f t) 

A potem zrobić coś takiego:

foldlMult' (min, max, (+), (*)) (head x, head x, 0, 0) x 

zrobić wiele fałdy w jednym przebiegu. Rozszerzenia GHC są w porządku.

+0

Myślę, że istnieje rozwiązanie przy użyciu Arrow's *** i &&&, używając typów takich jak (f, (g, (h, i)) zamiast (f, g, h, i) pod maską, ale ja ' kilkaset mil od mojego laptopa, więc nie mogę dziś zagrać. – AndrewC

Odpowiedz

10

Jeśli dobrze rozumiem twoje przykłady, typy są ai -> b -> ai, a nie ai -> b -> a, tak jak napisałeś po raz pierwszy. Przeredagujmy typy na a -> ri -> ri, tylko dlatego, że pomaga mi myśleć.

Pierwszą rzeczą obserwować jest to korespondencja:

(a -> r1 -> r1, ..., a -> rn -> rn) ~ a -> (r1 -> r1, ..., rn -> rn) 

To pozwala na pisanie tych dwóch funkcji, które są odwrotne:

pullArg :: (a -> r1 -> r1, a -> r2 -> r2) -> a -> (r1 -> r1, r2 -> r2) 
pullArg (f, g) = \a -> (f a, g a) 

pushArg :: (a -> (r1 -> r1, r2 -> r2)) -> (a -> r1 -> r1, a -> r2 -> r2) 
pushArg f = (\a -> fst (f a), \a -> snd (f a)) 

Drugie spostrzeżenie: typy postaci ri -> ri są czasami nazywane endomorfizmów, a każdy z tych typów ma monoid z kompozycją jako operacją asocjacyjną i funkcją tożsamości jako tożsamością. Pakiet Data.Monoid ma to opakowanie na to:

newtype Endo a = Endo { appEndo :: a -> a } 

instance Monoid (Endo a) where 
    mempty = id 
    mappend = (.) 

To pozwala Ci przepisać wcześniejszy pullArg do tego:

pullArg :: (a -> r1 -> r1, a -> r2 -> r2) -> a -> (Endo r1, Endo r2) 
pullArg (f, g) = \a -> (Endo $ f a, Endo $ g a) 

Trzecie spostrzeżenie: iloczyn dwóch monoids również monoid, jak na to przykład również z Data.Monoid:

instance (Monoid a, Monoid b) => Monoid (a, b) where 
    mempty = (mempty, mempty) 
    (a, b) `mappend` (c, d) = (a `mappend` c, b `mappend d) 

Podobnie na krotki dowolną liczbę argumentów.

Czwarte spostrzeżenie: What are folds made of? Odpowiedź: folds are made of monoids!

import Data.Monoid 

fold :: Monoid m => (a -> m) -> [a] -> m 
fold f = mconcat . map f 

Ten fold jest tylko specjalizacja foldMap z Data.Foldable, więc w rzeczywistości nie musimy go zdefiniować, możemy po prostu importować jej bardziej ogólną wersję:

foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m 

Jeśli fold z Endo, że jest taka sama jak składane z prawej strony.Spasować z lewej chcesz spasować z Dual (Endo r) monoid:

myfoldl :: (a -> Dual (Endo r)) -> r -> -> [a] -> r 
myfoldl f z xs = appEndo (getDual (fold f xs)) z 


-- From `Data.Monoid`. This just flips the order of `mappend`. 
newtype Dual m = Dual { getDual :: m } 

instance Monoid m => Monoid (Dual m) where 
    mempty = Dual mempty 
    Dual a `mappend` Dual b = Dual $ b `mappend` a 

Pamiętaj naszą pullArg funkcję? Załóżmy zmienić go nieco więcej, ponieważ jesteś składane od lewej:

pullArg :: (a -> r1 -> r1, a -> r2 -> r2) -> a -> Dual (Endo r1, Endo r2) 
pullArg (f, g) = \a -> Dual (Endo $ f a, Endo $ g a) 

i to, że twierdzą, jest to wersja 2-krotnym swoimi f lub przynajmniej izomorficzna do niego. Można byłaby swoje funkcje krotnie w postaci a -> Endo ri, a następnie wykonaj:

let (f'1, ..., f'n) = foldMap (pullArgn f1 ... fn) xs 
in (f'1 z1, ..., f'n zn) 

Warto także patrząc na: Composable Streaming Folds, który jest ponadto opracowanie tych pomysłów.

5

Do bezpośredniego podejścia, można po prostu określić odpowiedniki Control.Arrow „s (***) i (&&&) jawnie, dla każdego N (np N == 4):

prod4 (f1,f2,f3,f4) (x1,x2,x3,x4) = (f1 x1,f2 x2,f3 x3,f4 x4) -- cf (***) 
call4 (f1,f2,f3,f4) x   = (f1 x, f2 x, f3 x, f4 x) -- cf (&&&) 
uncurry4 f  (x1,x2,x3,x4) = f x1 x2 x3 x4 

Następnie

foldr4 :: (b -> a1 -> a1, b -> a2 -> a2, 
      b -> a3 -> a3, b -> a4 -> a4) 
     -> (a1, a2, a3, a4) -> [b] 
     -> (a1, a2, a3, a4)      -- (f .: g) x y = f (g x y) 
foldr4 t z xs = foldr (prod4 . call4 t) z xs  -- foldr . (prod4 .: call4) 
       -- f x1 (f x2 (... (f xn z) ...)) -- foldr . (($) .: ($)) 

Tak funkcje krotek w foldr4 to odwrócone wersje tego, co chciałeś. Testowanie:

Prelude> G Xs = foldr4 (min, maks (+), (*)) (xs, xs głowy głowy, 0, 1) xs
Prelude> G [1..5]
(1,5,15,120)

foldl4' jest wariację dalej. Od

foldr f z xs == foldl (\k x r-> k (f x r)) id xs z 
foldl f z xs == foldr (\x k a-> k (f a x)) id xs z 

mamy

foldl4, foldl4' :: (t -> a -> t, t1 -> a -> t1, 
        t2 -> a -> t2, t3 -> a -> t3) 
       -> (t, t1, t2, t3) -> [a] 
       -> (t, t1, t2, t3) 
foldl4 t z xs = foldr (\x k a-> k (call4 (prod4 t a) x)) 
         (prod4 (id,id,id,id)) xs z 
foldl4' t z xs = foldr (\x k a-> k (call4 (prod4' t a) x)) 
         (prod4 (id,id,id,id)) xs z 
prod4' (f1,f2,f3,f4) (x1,x2,x3,x4) = (f1 $! x1,f2 $! x2,f3 $! x3,f4 $! x4) 

Mamy nawet typy jak chcesz, dla funkcji krotki za.

Ostrzejsza wersja prod4 musiała zostać użyta, aby zmusić argumenty na wczesnym etapie foldl4'.