Haskell:如何映射元组?

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

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

给我[2,4] 。 有没有可以像这样工作的“mapTuple”function?

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

其结果是(2,4)

在Hoogle上search并不能完全匹配(a -> b) -> (a, a) -> (b, b) ,这是您需要的types,但是您自己做起来相当容易:

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

注意,你将不得不为3元组,4元组等定义一个新的函数,尽pipe这样的需求可能是一个标志,你不是像他们想要的那样使用元组:一般来说,元组持有不同types的值,所以想要对所有值应用单一函数并不是很常见。

一个相当短的解决scheme:

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

你可以使用Bifunctor

 import Control.Monad (join) import Data.Bifunctor (bimap) join bimap (2*) (1,2) 

这不仅适用于配对,而且适用于其他types,例如适用于Eithertypes。

Bifunctor的版本是4.8。 以前它是由bifunctors包提供的。

您可以使用模块Control.Arrow 箭头来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 

如果你的问题是你要求一个映射到任意元组元组的函数,那么恐怕你不能这样做,因为它们有不同的types(例如元组types(a,b)(a,b,c)是完全不同的和无关的)。

你也可以使用透镜来映射元组:

 import Control.Lens mapPair = over both 

或者,你可以映射元组中最多10个元素:

 mapNtuple f = traverseOf each (return . f) 

要添加另一个解决scheme到这个丰富多彩的集合…你也可以使用Scrap-Your-Boilerplategenerics编程映射任意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) 

请注意显式types注释是重要的,因为SYB按typesselect字段。 例如,如果使一个元组元素types为Float ,则不会再加倍。

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

这是另一种方式:

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

您需要Data.Function导入的function。

您也可以使用Applicatives,这些额外的好处是可以为每个元组元素应用不同的函数:

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

内联版本:

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

或者使用不同的地图function而不使用lambda:

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

其他的可能性是使用箭头:

 import Control.Arrow (+2) . fst &&& (+2) . snd $ (2, 3) 

额外的软件包在Data.Tuple.Extra模块中提供了bothfunction。 从文档:

 Apply a single function to both components of a pair. > both succ (1,2) == (2,3) both :: (a -> b) -> (a, a) -> (b, b) 

uniplate包在Data.Generics.Uniplate.Data模块中提供了下降函数。 这个函数会在types匹配的地方应用函数,所以可以应用到列表,元组,或者大多数其他数据types。 一些例子:

 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] 

我刚刚添加了一个包的元组 homogeneous -h98到解决这个问题。 它为元组添加了newtype包装器,并为它们定义了FunctorApplicativeFoldableTraversable实例。 使用这个软件包你可以做如下的事情:

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

或zip元组如:

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

是的,你会做:

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

fst抓取元组中的第一个数据项, snd抓取第二个数据项; 所以,代码行说:“取一个元组,返回另一个元组,第一个和第二个元素是前一个元素的两倍。