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 (***)
Bifunctor
を使用できます。
import Control.Monad (join)
import Data.Bifunctor (bimap)
join bimap (2*) (1,2)
これはペアだけでなく、他の多くのタイプでも機能します。 Either
の場合。
Bifunctor
は、バージョン4.8の時点で base にあります。以前は、 bifunctors パッケージによって提供されていました。
モジュール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)
異なる、無関係)。
lens を使用してタプルをマッピングすることもできます。
import Control.Lens
mapPair = over both
または、最大10個の要素を持つタプルにマップできます。
mapNtuple f = traverseOf each (return . f)
このカラフルなセットに別のソリューションを追加するには... 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倍になりません。
別の方法を次に示します。
mapPair :: (a -> b) -> (a, a) -> (b, b) -- this is the inferred type
mapPair f = uncurry ((,) `on` f)
on
関数用にインポートされたData.Function
が必要です。
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)
また、各タプル要素に異なる機能を適用できる可能性を提供する追加の利点があるアプリケーションを使用することもできます。
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)
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]
この問題を解決するパッケージ tuples-homogenous-h98 をHackageに追加しました。タプルのnewtype
ラッパーを追加し、Functor
、Applicative
、Foldable
およびTraversable
インスタンスを定義します。パッケージを使用すると、次のようなことができます。
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
は2番目のデータエントリを取得します。そのため、コード行には、「タプルを取得し、最初と2番目の項目が前の2倍になる別のタプルを返す」と書かれています。