Haskell:如何映射元组?

时间:2012-03-15 15:16:00

标签: haskell mapping tuples

在Haskell中,我可以轻松地映射列表:

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

给了我[2,4]。是否有任何“mapTuple”功能可以这样工作?

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

结果为(2,4)

13 个答案:

答案 0 :(得分:79)

这是一个相当短的无点解决方案:

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

mapTuple = join (***)

答案 1 :(得分:44)

Searching at 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 :(得分:23)

您可以使用模块Control.Arrow中的arrows来编写对元组有效的函数。

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)和{{1}完全不同且无关。)

答案 3 :(得分:23)

您可以使用Bifunctor

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

join bimap (2*) (1,2)

这不仅适用于配对,也适用于许多其他类型,例如: Either

截至4.8版,

Bifunctor位于base。以前它是由bifunctors包提供的。

答案 4 :(得分:19)

您还可以使用lens来映射元组:

import Control.Lens
mapPair = over both

或者您可以使用最多10个元素映射元组:

mapNtuple f = traverseOf each (return . f)

答案 5 :(得分:12)

要为这个彩色集添加另一个解决方案......您还可以使用Scrap-Your-Boilerplate generic programming映射任意n元组。例如:

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,它将不会再加倍。

答案 6 :(得分:11)

这是另一种方式:

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

您需要为Data.Function功能导入on

答案 7 :(得分:9)

是的,对于2个项目的元组,您可以使用firstsecond来映射元组的内容(不要担心类型签名;可以读取a b c在这种情况下为b -> c)。对于较大的元组,您应该考虑使用数据结构和镜头。

答案 8 :(得分:7)

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)

答案 9 :(得分:6)

您还可以使用Applicatives,它具有额外的好处,可以为每个元组元素应用不同的函数:

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)

或使用不同的地图函数且没有lambda:

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

其他可能性是使用箭头:

import Control.Arrow

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

答案 10 :(得分:4)

我刚刚向Hackage添加了一个包tuples-homogenous-h98来解决这个问题。它为元组添加newtype包装器,并为它们定义FunctorApplicativeFoldableTraversable个实例。使用该软件包,您可以执行以下操作:

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

或zip元组如:

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

答案 11 :(得分:4)

uniplate包在descend模块中提供Data.Generics.Uniplate.Data功能。此函数将在函数匹配的任何位置应用函数,因此可以应用于列表,元组,Either或大多数其他数据类型。一些例子:

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]

答案 12 :(得分:3)

是的,你会这样做:

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

fst抓取元组中的第一个数据条目,snd抓取第二个;所以,代码行说“取一个元组,并返回另一个元组,第一个和第二个项目加倍。”