Haskell: как нарисовать кортеж?

В Haskell я могу легко отобразить список:

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

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

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

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

Ответ 1

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

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

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

Ответ 2

Здесь довольно короткое решение без точек:

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

mapTuple = join (***)

Ответ 3

Вы можете использовать arrows из модуля 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

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

Ответ 4

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

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

join bimap (2*) (1,2)

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

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

Ответ 5

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

import Control.Lens
mapPair = over both

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

mapNtuple f = traverseOf each (return . f)

Ответ 6

Чтобы добавить другое решение к этому красочному набору... Вы также можете отобразить над произвольными 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 выбирает поля по типу. Например, если один тип элемента tuple Float, он больше не будет удваиваться.

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

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)

Ответ 10

Пакет 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)

Ответ 11

Я только добавил пакет tuples-homogenous-h98 в Hackage, который решает эту проблему. Он добавляет обертки newtype для кортежей и определяет для них экземпляры Functor, Applicative, Foldable и Traversable. Используя пакет, вы можете делать такие вещи, как:

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

или zip кортежи вроде:

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

Ответ 12

Пакет uniplate предоставляет descend в модуле Data.Generics.Uniplate.Data. Эта функция будет применять функцию везде, где совпадают типы, поэтому ее можно применять к спискам, кортежам, любым или другим типам данных. Некоторые примеры:

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]

Ответ 13

Да, вы бы сделали:

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

fst захватывает первый ввод данных в кортеже, а snd захватывает второй; поэтому строка кода говорит: "Возьмите кортеж и верните другой кортеж с первым и вторым элементами, двойным предыдущим".