В Haskell я могу легко отобразить список:
map (\x -> 2*x) [1,2]
дает мне [2,4]
. Есть ли какая-либо функция "mapTuple", которая будет работать так?
mapTuple (\x -> 2*x) (1,2)
с результатом (2,4)
.
В Haskell я могу легко отобразить список:
map (\x -> 2*x) [1,2]
дает мне [2,4]
. Есть ли какая-либо функция "mapTuple", которая будет работать так?
mapTuple (\x -> 2*x) (1,2)
с результатом (2,4)
.
Поиск в Hoogle не дает точных совпадений для (a -> b) -> (a, a) -> (b, b)
, который вам нужен, но это довольно легко сделать самому:
mapTuple :: (a -> b) -> (a, a) -> (b, b)
mapTuple f (a1, a2) = (f a1, f a2)
Обратите внимание: вам нужно будет определить новую функцию для 3-кортежей, 4-кортежей и т.д. - хотя такая потребность может быть признаком того, что вы не используете кортежи, как они были предназначены: в общем случае кортежи содержат значения разные типы, поэтому очень важно не применять одну функцию ко всем значениям.
Здесь довольно короткое решение без точек:
import Control.Monad (join)
import Control.Arrow ((***))
mapTuple = join (***)
Вы можете использовать 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)
совершенно разные и не связаны).
Вы можете использовать Bifunctor
:
import Control.Monad (join)
import Data.Bifunctor (bimap)
join bimap (2*) (1,2)
Это работает не только для пар, но и для ряда других типов, например. для Either
.
Bifunctor
находится в base с версии 4.8. Ранее это было предоставлено пакетом bifunctors.
Вы также можете использовать lens для сопоставления кортежей:
import Control.Lens
mapPair = over both
Или вы можете сопоставить кортежи с до 10 элементами:
mapNtuple f = traverseOf each (return . f)
Чтобы добавить другое решение к этому красочному набору... Вы также можете отобразить над произвольными 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
, он больше не будет удваиваться.
Да, для кортежей из 2 элементов вы можете использовать first
и second
, чтобы отобразить содержимое кортежа (не беспокойтесь о сигнатуре типа; a b c
можно прочитать как b -> c
в этой ситуации). Для больших кортежей вам следует использовать структуру данных и объективы.
Вот еще один способ:
mapPair :: (a -> b) -> (a, a) -> (b, b) -- this is the inferred type
mapPair f = uncurry ((,) `on` f)
Вам нужно Data.Function
импортировать для функции on
.
Вы также можете использовать Применения, которые имеют дополнительное преимущество, давая вам возможность применять различные функции для каждого элемента кортежа:
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)
Пакет 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)
Я только добавил пакет tuples-homogenous-h98 в Hackage, который решает эту проблему. Он добавляет обертки newtype
для кортежей и определяет для них экземпляры Functor
, Applicative
, Foldable
и Traversable
. Используя пакет, вы можете делать такие вещи, как:
untuple2 . fmap (2 *) . Tuple2 $ (1, 2)
или zip кортежи вроде:
Tuple2 ((+ 1), (*2)) <*> Tuple2 (1, 10)
Пакет 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]
Да, вы бы сделали:
map (\x -> (fst x *2, snd x *2)) [(1,2)]
fst
захватывает первый ввод данных в кортеже, а snd
захватывает второй; поэтому строка кода говорит: "Возьмите кортеж и верните другой кортеж с первым и вторым элементами, двойным предыдущим".