2012-03-15 16 views
48

W Haskell, mogę łatwo mapować listę:Haskell: jak zmapować krotkę?

map (\x -> 2*x) [1,2] 

daje mi [2,4]. Czy istnieje funkcja "mapTuple", która działałaby w ten sposób?

mapTuple (\x -> 2*x) (1,2) 

z wynikiem bycia (2,4).

+0

BTW, nie trzeba używać lambdas do mapowania prostego mnożenia na liście: po prostu 'map (* 2) [1,2,3]' zrobiłoby lewę. –

+1

Zauważ, że bez względu na to, jak to zaimplementujesz, będzie działać tylko na krotkach z typem: (a, a) – hololeap

Odpowiedz

41

Searching at Hoogle nie podaje dokładnych wyników dla (a -> b) -> (a, a) -> (b, b), która jest typem jest potrzebna, ale jest to dość łatwo zrobić samemu:

mapTuple :: (a -> b) -> (a, a) -> (b, b) 
mapTuple f (a1, a2) = (f a1, f a2) 

Uwaga, trzeba będzie zdefiniować nową funkcję dla 3-krotki, 4-krotki itp - choć taka potrzeba może być oznaką, że nie używasz krotki jakby były przeznaczone: W ogóle, krotki przytrzymaj wartości różnych typów, więc chcąc zastosować pojedynczą funkcję do wszystkich wartości nie jest bardzo częste.

+0

Dzięki, oto odpowiedź, której szukałem. –

+7

'Control.Arrow'' jest * w standardowej bibliotece, a' Control.Bifunctor' nie jest zbyt daleko ... – Landei

+0

@Landei: Tak, ale OP zapytał o mapowanie pojedynczej funkcji przez krotkę. – Boris

3

Tak, zrobiłbyś:

map (\x -> (fst x *2, snd x *2)) [(1,2)] 

fst chwyta pierwszy wpis danych w krotce i snd łapie drugi; tak więc wiersz kodu mówi "weź krotkę i zwróć kolejną krotkę, a pierwsze i drugie elementy podwoją poprzednią."

+1

Twój kod nie działa, ale na marginesie pamiętaj, że możesz dopasować wzorzec przy argumentach do funkcji lambda : '\ (x, y) -> (x * 2, y * 2)' – danr

+0

@danr - Brakowało mi nawiasu, dzięki za wskazanie: P. Nie wiedziałem jednak o dopasowywaniu wzorów, dzięki! –

+0

@danr To działa dla mnie. –

9

Tak, na krotki z 2 pozycji, można użyć first i second mapować zawartość krotki (nie martw się o podpisaniu typu; a b c można odczytać jako b -> c w tej sytuacji). W przypadku większych krotek powinieneś rozważyć użycie struktury danych i soczewek.

22

Możesz użyć z modułu Control.Arrow, aby komponować funkcje działające na krotkach.

Prelude Control.Arrow> let f = (*2) *** (*2) 
Prelude Control.Arrow> f (1,2) 
(2,4) 
Prelude Control.Arrow> let f' = (*2) *** (*3) 
Prelude Control.Arrow> f (2,2) 
(4,4) 
Prelude Control.Arrow> f' (2,2) 
(4,6) 

Twój mapTuple staje

mapTuple f = f *** f 

Jeśli z Twojego pytania zadawane dla funkcji, która odwzorowuje na krotki arbitralnych liczbę operandów, to obawiam się, że nie może, bo musiałby różne rodzaje (np. typy krotek (a,b) i (a,b,c) są całkowicie różne i niezwiązane).

+0

Czy muszę załadować moduł, aby z nich skorzystać? W zwykłym Prelude twój kod nie działa. –

+0

Tak, robisz, potrzebujesz 'Control.Arrow'. Zaktualizowałem swoją odpowiedź. –

22

Można użyć Bifunctor:

import Control.Monad (join) 
import Data.Bifunctor (bimap) 

join bimap (2*) (1,2) 

To działa nie tylko dla par, ale dla wielu innych rodzajów, jak również, na przykład dla Either.

Bifunctor jest w wersji base od wersji 4.8. Poprzednio był dostarczany przez pakiet bifunctors.

+0

Sztuczka 'join' dla instancji' (->) 'monad jest niesamowita. Dzięki! – Profpatsch

+0

Nice! To bije to, co znalazłem w innym kontekście, gdzie chciałem tylko zmodyfikować drugą wartość, tj. Import'Data.Graph.Indukcyjny.Query.Monad', a następnie '(* 2)><(* 2)' to funkcja, którą Później. (Jestem zdziwiony, że to nie jest w Data.Tuple, ale może to tylko przypadek historii.) –

71

Oto raczej krótki punkt wolne rozwiązanie:

import Control.Monad (join) 
import Control.Arrow ((***)) 

mapTuple = join (***) 
+0

Jaki jest efekt sprzężenia, gdy jest używany w funkcjach? –

+12

@Riccardo - 'join' przyjmuje funkcję dwóch argumentów tego samego typu,' a-> a-> b', i tworzy nową funkcję z jednym argumentem 'a -> b', przekazując ten argument do obu pozycji oryginalna funkcja. Dzieje się tak, ponieważ instancja Monad dla funkcji jest identyczna z monadą 'Reader', dając' join' type '(a -> a -> b) -> a -> b'. Uważam, że nieco łatwiej jest dojść do wniosku, gdy strzałki nie są zapisywane jako infiks, np. 'join :: (a ->) ((a ->) b) -> (a ->) b'. –

+0

@JohnL: thanks! –

12

Aby dodać inne rozwiązanie do tego kolorowego zestawu ... Można również mapować na dowolnych n-krotki używając Scrap-Your-Boilerplate generic programming.Na przykład:

import Data.Data 
import Data.Generics.Aliases 

double :: Int -> Int 
double = (*2) 

tuple :: (Int, Int, Int, Int) 
tuple = gmapT (mkT double) (1,2,3,4) 

nocie, że wyraźne adnotacje typu są ważne, ponieważ SYB wybiera pola według typu. Jeśli na przykład zostanie utworzony jeden element krotki: Float, nie będzie on już więcej podwójny.

+1

Rozumiem, że wiele oryginalnych bibliotek SYB zostało zastąpionych przez GHC Generics z pewną obsługą w bazie/kompilatorze. (Ale może ty i ta strona wikipedii używają słowa "SYB" jako ogólnego terminu dla generycznych, a nie tylko konkretnych oryginalnych dokumentów SYB ...) – misterbee

+3

Mogę się mylić, ale biorąc pod uwagę, że GHC [dokumentacja "Data.Data"] (http://hackage.haskell.org/packages/archive/base/latest/doc/html/Data-Data.html) cytuje SYB, założę się, że jest to bezpośredni potomek. –

6

Można również użyć Applicatives które mają dodatkową zaletę, że daje możliwość zastosowania różnych funkcji dla każdego elementu krotki: Wersja

import Control.Applicative 

mapTuple :: (a -> a') -> (b -> b') -> (a, b) -> (a', b') 
mapTuple f g = (,) <$> f . fst <*> g . snd 

Inline:

(\f -> (,) <$> f . fst <*> f . snd) (*2) (3, 4) 

lub z różnych funkcji map i bez lambda:

(,) <$> (*2) . fst <*> (*7) . snd $ (3, 4) 

Inne możliwością byłoby użyć strzałek:

import Control.Arrow 

(+2) . fst &&& (+2) . snd $ (2, 3) 
7

Oto kolejny sposób:

mapPair :: (a -> b) -> (a, a) -> (b, b) -- this is the inferred type 
mapPair f = uncurry ((,) `on` f) 

Trzeba Data.Function importowane do on funkcji.

+0

Myślę, że to najprostsze i najbardziej eleganckie rozwiązanie. +1 – hololeap

4

Właśnie dodany pakiet tuples-homogenous-h98 do Hackage, która rozwiązuje ten problem. Dodaje newtype owijarki za krotki i definiuje Functor, Applicative, Foldable i Traversable instancje dla nich. Korzystanie z pakietu można robić takie rzeczy jak:

untuple2 . fmap (2 *) . Tuple2 $ (1, 2) 

lub zip krotki jak:

Tuple2 ((+ 1), (*2)) <*> Tuple2 (1, 10) 
18

Można również użyć lens mapować krotki:

import Control.Lens 
mapPair = over both 

Albo można odwzorować na krotki z maksymalnie 10 elementami:

mapNtuple f = traverseOf each (return . f) 
+2

'over each' wydaje się również działać:' (over each) (+1) (1,2,3,4,5,6,7,8,9) == (2,3,4,5 , 6,7,8,9,10) " – Wizek

5

Pakiet extra zapewnia funkcję both w module Data.Tuple.Extra. Od docs:

Apply a single function to both components of a pair. 

> both succ (1,2) == (2,3) 

both :: (a -> b) -> (a, a) -> (b, b) 
4

Pakiet uniplate udostępnia funkcję w module Data.Generics.Uniplate.Datadescend. Ta funkcja zastosuje tę funkcję wszędzie, gdzie typy są zgodne, więc może być zastosowana do list, krotek, albo do większości innych typów danych. Kilka przykładów:

descend (\x -> 2*x) (1,2) == (2,4) 
descend (\x -> 2*x) (1,"test",Just 2) == (2,"test",Just 4) 
descend (\x -> 2*x) (1,2,3,4,5) == (2,4,6,8,10) 
descend (\x -> 2*x) [1,2,3,4,5] == [2,4,6,8,10] 
Powiązane problemy