Haskell: как отобразить кортеж? - PullRequest
59 голосов
/ 15 марта 2012

В Хаскеле я могу легко сопоставить список:

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

дает мне [2,4]. Есть ли какая-нибудь функция mapTuple, которая бы работала так?

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

с результатом (2,4).

Ответы [ 13 ]

79 голосов
/ 15 марта 2012

Вот довольно короткое бессмысленное решение:

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

mapTuple = join (***)
44 голосов
/ 15 марта 2012

Поиск в Hoogle не дает точных совпадений для (a -> b) -> (a, a) -> (b, b), типа, который вам требуется, но это довольно легко сделать самостоятельно:

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

Обратите внимание, вам придется определить новую функцию для 3-х кортежей, 4-х кортежей и т. Д. - хотя такая необходимость может быть признаком того, что вы не используете кортежи так, как они предназначались: в общем, кортежи содержат значения разные типы, поэтому желание применять одну функцию ко всем значениям не очень распространено.

23 голосов
/ 15 марта 2012

Вы можете использовать Bifunctor:

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

join bimap (2*) (1,2)

Это работает не только для пар, но и для ряда других типов, например, для Either.

Bifunctor в базе с версии 4.8. Ранее он был предоставлен пакетом bifunctors .

23 голосов
/ 15 марта 2012

Вы можете использовать стрелки из модуля Control.Arrow для составления функций, которые работают с кортежами.

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)

Тогда ваш mapTuple становится

mapTuple f = f *** f

Если сВаш вопрос вы задали для функции, которая отображает кортежи произвольной арности, поэтому я боюсь, что вы не можете, потому что они будут иметь разные типы (например, типы кортежей (a,b) и (a,b,c) совершенно разные и не связаны).

19 голосов
/ 11 января 2014

Вы также можете использовать lens для отображения кортежей:

import Control.Lens
mapPair = over both

Или вы можете отобразить кортежи с 10 элементами:

mapNtuple f = traverseOf each (return . f)
12 голосов
/ 15 марта 2012

Чтобы добавить другое решение к этому красочному набору ... Вы также можете нанести на карту произвольные n-кортежей, используя Scrap-Your-Boilerplate универсальное программирование . Например:

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)

Обратите внимание, что явные аннотации типов важны, так как SYB выбирает поля по типу. Например, если создать один элемент кортежа типа Float, он больше не удваивается.

11 голосов
/ 16 августа 2013

Вот еще один способ:

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

Вам нужно Data.Function импортировать для on функции.

9 голосов
/ 15 марта 2012

Да, для кортежей из 2 элементов вы можете использовать first и second для отображения содержимого кортежа (Не беспокойтесь о сигнатуре типа;a b c может читаться как b -> c в этой ситуации).Для больших кортежей вы должны рассмотреть возможность использования структуры данных и линз.

7 голосов
/ 16 ноября 2014

Пакет extra обеспечивает функцию both в модуле Data.Tuple.Extra . Из документов:

Apply a single function to both components of a pair.

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

both :: (a -> b) -> (a, a) -> (b, b)
6 голосов
/ 18 апреля 2013

Вы также можете использовать Аппликативы, которые имеют дополнительное преимущество, позволяя вам применять различные функции для каждого элемента кортежа:

import Control.Applicative

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

Встроенная версия:

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

или с другой картойфункции и без лямбды:

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

Другая возможность будет использовать стрелки:

import Control.Arrow

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