L'ordre de manipuler des arguments aux constructeurs de type
Question
J'ai écrit quelque chose comme ceci:
instance Functor (Either e) where
fmap _ (Left a) = Left a
fmap f (Right b) = Right (f b)
Comment puis-je faire la même chose si je veux fmap
changer la valeur que si elle est Left
?
Je veux dire, ce que la syntaxe dois-je utiliser pour indiquer que j'utilise le type Either _ b
au lieu de Either a _
?
La solution
Je ne pense pas qu'il y ait une façon de le faire directement, malheureusement. Avec une fonction, vous pouvez utiliser flip
pour appliquer partiellement le deuxième argument, mais cela ne fonctionne pas avec les constructeurs de type comme Either
.
La chose la plus simple est probablement envelopper dans un newtype
:
newtype Mirror b a = Mirrored (Either a b)
instance Functor (Mirror e) where
fmap _ (Mirrored (Right a)) = Mirrored $ Right a
fmap f (Mirrored (Left b)) = Mirrored $ Left (f b)
Emballage avec newtype
est également le moyen standard pour créer plusieurs instances pour un seul type, comme Sum
et Product
étant des cas de Monoid
pour les types numériques. Sinon, vous ne pouvez avoir qu'une seule instance par type.
De plus, en fonction de ce que vous voulez faire, une autre option est d'ignorer Functor
et définir votre propre classe de type comme ceci:
class Bifunctor f where
bimap :: (a -> c) -> (b -> d) -> f a b -> f c d
instance Bifunctor Either where
bimap f _ (Left a) = Left $ f a
bimap _ g (Right b) = Right $ g b
instance Bifunctor (,) where
bimap f g (a, b) = (f a, g b)
De toute évidence, cette classe est deux fois plus amusant comme Functor
régulier. Bien sûr, vous ne pouvez pas faire une instance Monad
de très facilement.
Autres conseils
Vous ne pouvez pas faire l'instance que vous cherchez directement.
Pour l'inférence de type et les classes de type de travail, il y a un certain parti pris de position à l'ordre des arguments dans les types. Il a été démontré que si l'on a permis réordonnancement arbitraire des arguments lors de l'instanciation des classes de type, que l'inférence de type devient intraitable.
Vous pouvez utiliser un Bifunctor
classe qui peut mapper sur les deux arguments séparément.
class Bifunctor f where
bimap :: (a -> b) -> (c -> d) -> f a c -> f b d
first :: (a -> b) -> f a c -> f b c
second :: (c -> d) -> f a c -> f a d
first f = bimap f id
second = bimap id
instance Bifunctor Either where
bimap f _ (Left a) = Left (f a)
bimap _ g (Right b) = Right (g b)
instance Bifunctor (,) where
bimap f g (a,b) = (f a, g b)
Ou vous pouvez utiliser un Flip
Combinator comme:
newtype Flip f a b = Flip { unFlip :: f b a }
versions Generalized de ces deux sont disponibles dans la catégorie-extras sur hackage. Celui-ci inclut même une instance pour Functor (Flip Either a)
parce Either
est un Bifunctor
. (Je devrais probablement corriger cela seulement besoin d'un PFunctor
)
En fin de compte, l'ordre des arguments dans un constructeur de type est important pour déterminer quelles sont les classes que vous pouvez instancier. Vous devrez peut-être utiliser des emballages de New Type (comme Flip
ci-dessus) pour mettre les arguments où ils ont besoin pour se qualifier pour construire une instance d'une autre classe de types. C'est le prix que nous payons pour la déduction des contraintes de classe de type.
Vous avez besoin essentiellement d'un combinateur « flip » sur les types. Une enveloppe de newtype qui inverse l'ordre devrait fonctionner, comme le dit camccann. Notez que vous ne pouvez pas utiliser un synonyme « type », car ils ne peuvent pas être appliquées en partie.