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 _?

Était-ce utile?

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top