web-dev-qa-db-fra.com

Haskell: comment cartographier un tuple?

À Haskell, je peux facilement mapper une liste:

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

donne moi [2,4]. Y a-t-il une fonction "mapTuple" qui fonctionnerait comme ça?

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

le résultat étant (2,4).

59
quant_dev

Recherche sur Hoogle ne donne aucune correspondance exacte pour (a -> b) -> (a, a) -> (b, b), qui est le type dont vous avez besoin, mais il est assez facile de le faire vous-même:

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

Remarque, vous devrez définir une nouvelle fonction pour les 3-tuples, 4-tuples, etc. il n'est donc pas très courant de vouloir appliquer une seule fonction à toutes les valeurs.

44
Boris

Voici une solution sans point assez courte:

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

mapTuple = join (***)
79
Rotsor

Vous pouvez utiliser Bifunctor:

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

join bimap (2*) (1,2)

Cela fonctionne non seulement pour les paires, mais aussi pour un certain nombre d'autres types, par exemple pour Either.

Bifunctor est dans base à partir de la version 4.8. Auparavant, il était fourni par le paquet bifunctors .

24
Landei

Vous pouvez utiliser flèches à partir du module Control.Arrow pour composer des fonctions qui fonctionnent sur des 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)

Votre mapTuple devient alors

mapTuple f = f *** f

Si, avec votre question, vous avez demandé une fonction qui mappe sur des tuples d'arité arbitraire, alors je crains que vous ne puissiez pas, car ils auraient des types différents (par exemple, les types de tuple (a,b) et (a,b,c) sont totalement différents et indépendants).

23
Riccardo T.

Vous pouvez également utiliser lens pour mapper les tuples:

import Control.Lens
mapPair = over both

Ou vous pouvez mapper des tuples avec jusqu'à 10 éléments:

mapNtuple f = traverseOf each (return . f)
20
Sebastian Wagner

Pour ajouter une autre solution à cet ensemble coloré ... Vous pouvez également cartographier des n-tuples arbitraires en utilisant programmation générique Scrap-Your-Boilerplate . Par exemple:

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)

Notez que les annotations de type explicites sont importantes, car SYB sélectionne les champs par type. Si on fait un type d'élément Tuple Float, par exemple, il ne serait plus doublé.

12
Peter Wortmann

Voici une autre façon:

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

Vous avez besoin Data.Function importé pour la fonction on.

12
ciuncan

Oui, pour des tuples de 2 éléments, vous pouvez utiliser first et second pour mapper le contenu d'un tuple (ne vous inquiétez pas à propos de la signature de type; a b c peut être lu comme b -> c dans cette situation). Pour les tuples plus gros, vous devriez plutôt envisager d'utiliser une structure de données et des lentilles.

9
dflemstr

Le package extra fournit la fonction both dans le module Data.Tuple.Extra . De la documentation:

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

Vous pouvez également utiliser des Applicatifs qui ont l'avantage supplémentaire de vous donner la possibilité d'appliquer différentes fonctions pour chaque élément Tuple:

import Control.Applicative

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

Version en ligne:

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

ou avec différentes fonctions cartographiques et sans lambda:

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

Une autre possibilité serait d'utiliser des flèches:

import Control.Arrow

(+2) . fst &&& (+2) . snd $ (2, 3)
6
Piotr Kukielka

Le package niplate fournit la fonction descend dans le module Data.Generics.Uniplate.Data . Cette fonction appliquera la fonction partout où les types correspondent, elle peut donc être appliquée aux listes, aux tuples, à l'un ou à la plupart des autres types de données. Quelques exemples:

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

Je viens d'ajouter un package tuples-homogenous-h98 à Hackage qui résout ce problème. Il ajoute des enveloppes newtype pour les tuples et définit pour eux des instances Functor, Applicative, Foldable et Traversable. En utilisant le package, vous pouvez faire des choses comme:

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

ou Zip tuples comme:

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

Oui, vous feriez:

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

fst récupère la première entrée de données dans un Tuple et snd récupère la seconde; ainsi, la ligne de code dit "prenez un tuple et renvoyez un autre tuple avec les premier et deuxième éléments doublant le précédent".

3
Marshall Conover