web-dev-qa-db-ja.com

Haskell:タプルをマップする方法は?

Haskellでは、リストを簡単にマップできます。

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

[2,4]。そのように機能する「mapTuple」関数はありますか?

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

結果は(2,4)

59
quant_dev

Hoogleで検索 は、必要なタイプである(a -> b) -> (a, a) -> (b, b)に完全に一致しませんが、自分で簡単に実行できます。

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

3タプル、4タプルなどの新しい関数を定義する必要があることに注意してください-そのような必要性はサインであるかもしれませんが、意図したタプルを使用していないということです:一般に、タプルは異なるタイプの値を保持し、そのため、すべての値に単一の関数を適用することはあまり一般的ではありません。

44
Boris

かなり短いポイントフリーのソリューションを次に示します。

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

mapTuple = join (***)
79
Rotsor

Bifunctorを使用できます。

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

join bimap (2*) (1,2)

これはペアだけでなく、他の多くのタイプでも機能します。 Eitherの場合。

Bifunctorは、バージョン4.8の時点で base にあります。以前は、 bifunctors パッケージによって提供されていました。

24
Landei

モジュール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

あなたの質問で、任意のアリティのタプルにマップする関数を要求した場合、それらは異なるタイプを持つため、できないと思います(例えば、タプルのタイプ(a,b)(a,b,c)異なる、無関係)。

23
Riccardo T.

lens を使用してタプルをマッピングすることもできます。

import Control.Lens
mapPair = over both

または、最大10個の要素を持つタプルにマップできます。

mapNtuple f = traverseOf each (return . f)
20

このカラフルなセットに別のソリューションを追加するには... 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はタイプごとにフィールドを選択するため、明示的なタイプアノテーションが重要であることに注意してください。たとえば、1つのTuple要素タイプFloatを作成した場合、それはもう2倍になりません。

12
Peter Wortmann

別の方法を次に示します。

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

on関数用にインポートされたData.Functionが必要です。

12
ciuncan

はい、2項目のタプルの場合、 first および second を使用して、タプルの内容をマップできます(心配しないでください)型シグネチャについて; a b cb -> cこの状況では)。大きなタプルの場合は、代わりにデータ構造とレンズの使用を検討する必要があります。

9
dflemstr

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)
7
Neil Mitchell

また、各タプル要素に異なる機能を適用できる可能性を提供する追加の利点があるアプリケーションを使用することもできます。

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)
6
Piotr Kukielka

niplate パッケージは、 Data.Generics.Uniplate.Data モジュールの descend 関数を提供します。この関数は、型が一致するすべての場所に関数を適用するため、リスト、タプル、いずれか、または他のほとんどのデータ型に適用できます。いくつかの例:

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]
4
Neil Mitchell

この問題を解決するパッケージ tuples-homogenous-h98 をHackageに追加しました。タプルのnewtypeラッパーを追加し、FunctorApplicativeFoldableおよびTraversableインスタンスを定義します。パッケージを使用すると、次のようなことができます。

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

または次のようなZipタプル:

Tuple2 ((+ 1), (*2)) <*> Tuple2 (1, 10)
4
Petr Pudlák

はい、あなたはそうするでしょう:

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

fstはタプルの最初のデータエントリを取得し、sndは2番目のデータエントリを取得します。そのため、コード行には、「タプルを取得し、最初と2番目の項目が前の2倍になる別のタプルを返す」と書かれています。

3