Haskell: how to map a tuple?

HaskellMappingTuples

Haskell Problem Overview


In Haskell, I can easily map a list:

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

gives me [2,4]. Is there any "mapTuple" function which would work like that?

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

with the result being (2,4).

Haskell Solutions


Solution 1 - Haskell

Here's a rather short point-free solution:

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

mapTuple = join (***)

Solution 2 - Haskell

Searching at Hoogle gives no exact matches for (a -> b) -> (a, a) -> (b, b), which is the type you require, but it is pretty easy to do yourself:

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

Note, you will have to define a new function for 3-tuples, 4-tuples etc - although such a need might be a sign, that you are not using tuples like they were intended: In general, tuples hold values of different types, so wanting to apply a single function to all values is not very common.

Solution 3 - Haskell

You could use Bifunctor:

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

join bimap (2*) (1,2)

This works not only for pairs, but for a number of other types as well, e.g. for Either.

Bifunctor is in base as of version 4.8. Previously it was provided by the bifunctors package.

Solution 4 - Haskell

You can use arrows from module Control.Arrow to compose functions that work on tuples.

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)

Your mapTuple then becomes

mapTuple f = f *** f

If with your question you asked for a function that maps over tuples of arbitrary arity, then I'm afraid you can't because they would have different types (e.g. the tuple types (a,b) and (a,b,c) are totally different and unrelated).

Solution 5 - Haskell

You can also use lens to map tuples:

import Control.Lens
mapPair = over both

Or you can map over tuples with upto 10 elements:

mapNtuple f = traverseOf each (return . f)

Solution 6 - Haskell

Here is another way:

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

You need Data.Function imported for on function.

Solution 7 - Haskell

To add another solution to this colourful set... You can also map over arbitrary n-tuples using Scrap-Your-Boilerplate generic programming. For example:

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)

Note that the explicit type annotations are important, as SYB selects the fields by type. If one makes one tuple element type Float, for example, it wouldn't be doubled anymore.

Solution 8 - Haskell

Yes, for tuples of 2 items, you can use first and second to map the contents of a tuple (Don't worry about the type signature; a b c can be read as b -> c in this situation). For larger tuples, you should consider using a data structure and lenses instead.

Solution 9 - Haskell

The extra package provides the both function in the Data.Tuple.Extra module. From the docs:

Apply a single function to both components of a pair.

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

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

Solution 10 - Haskell

You can also use Applicatives which have additional benefit of giving you possibility to apply different functions for each tuple element:

import Control.Applicative

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

Inline version:

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

or with different map functions and without lambda:

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

Other possibility would be to use Arrows:

import Control.Arrow

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

Solution 11 - Haskell

I just added a package tuples-homogenous-h98 to Hackage that solves this problem. It adds newtype wrappers for tuples and defines Functor, Applicative, Foldable and Traversable instances for them. Using the package you can do things like:

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

or zip tuples like:

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

Solution 12 - Haskell

The uniplate package provides the descend function in the Data.Generics.Uniplate.Data module. This function will apply the function everywhere the types match, so can be applied to lists, tuples, Either, or most other data types. Some examples:

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]

Solution 13 - Haskell

Yes, you would do:

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

fst grabs the first data entry in a tuple, and snd grabs the second; so, the line of code says "take a tuple, and return another tuple with the first and second items double the previous."

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionquant_devView Question on Stackoverflow
Solution 1 - HaskellRotsorView Answer on Stackoverflow
Solution 2 - HaskellBorisView Answer on Stackoverflow
Solution 3 - HaskellLandeiView Answer on Stackoverflow
Solution 4 - HaskellRiccardo T.View Answer on Stackoverflow
Solution 5 - HaskellSebastian WagnerView Answer on Stackoverflow
Solution 6 - HaskellciuncanView Answer on Stackoverflow
Solution 7 - HaskellPeter WortmannView Answer on Stackoverflow
Solution 8 - HaskelldflemstrView Answer on Stackoverflow
Solution 9 - HaskellNeil MitchellView Answer on Stackoverflow
Solution 10 - HaskellPiotr KukielkaView Answer on Stackoverflow
Solution 11 - HaskellPetrView Answer on Stackoverflow
Solution 12 - HaskellNeil MitchellView Answer on Stackoverflow
Solution 13 - HaskellMarshall ConoverView Answer on Stackoverflow