2010-02-25 5 views
11

j'ai écrit quelque chose comme ceci:l'ordre des Manipuler arguments de type constructeurs

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 pour changer la valeur que si elle est Left?

Je veux dire, quelle syntaxe est-ce que j'utilise pour indiquer que j'utilise le type Either _ b au lieu de Either a _?

+1

En relation: http://stackoverflow.com/questions/1827645 En bref, ce n'est pas possible dans Haskell tel qu'il est actuellement. – ephemient

+0

En aparté, le titre de cette question n'est pas très clair, mais je ne suis pas sûr de ce qui serait mieux. –

+0

J'ai proposé un titre plus précis. –

Répondre

7

Je ne pense pas qu'il existe un moyen de le faire directement, malheureusement. Avec une fonction, vous pouvez utiliser flip pour appliquer partiellement le second argument, mais cela ne fonctionne pas avec les constructeurs de type Either.

La chose la plus simple est l'enveloppant probablement 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, tels que Sum et Product cas étant de Monoid pour numérique les types. 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 celui-ci:

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 de plaisir en tant que régulier Functor. Bien sûr, vous ne pouvez pas créer une instance Monad très facilement.

+1

Qu'en est-il de 'newtype Flip t a b = Retourner (t b a)' et ensuite 'Functor d'instance (Flip Either e)'? –

+0

Cela ne fonctionne pas: "Tous les types d'instances doivent être de la forme (T a1 ... an) où a1 ... an sont type * variables *" – mik01aj

+0

@ m01: Cela fonctionne, mais nécessite l'activation d'une extension de langage GHC . Dans la pratique, je ferais soit (sans jeu de mots) faire comme le suggère Norman Ramsey, soit utiliser la classe 'Bifunctor' comme dans ma réponse. Le type «Mirror» sur-spécialisé était principalement destiné à illustrer l'idée. –

3

Vous avez essentiellement besoin d'un combinateur «flip» sur les types. Un wrapper newtype qui inverse la commande devrait fonctionner, comme le dit camccann. Notez que vous ne pouvez pas utiliser un synonyme «type», car ils ne peuvent pas être appliqués partiellement.

4

Vous ne pouvez pas créer l'instance que vous recherchez directement.

Pour que l'inférence de type et les classes de types fonctionnent, il existe un certain biais de position dans l'ordre des arguments dans les types. Il a été montré que si nous autorisions la réorganisation arbitraire des arguments lors de l'instanciation des classes de types, cette inférence de type devient intraitable.

Vous pouvez utiliser une classe Bifunctor qui peut mapper 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 Généralisées de ces deux sont disponibles dans la catégorie-extras sur hackage. Ce dernier inclut même une instance pour Functor (Flip Either a) car Either est un Bifunctor. (Je devrais probablement corriger cela pour ne demander qu'un PFunctor)

Finalement, l'ordre des arguments dans un constructeur de type est important pour déterminer quelles classes vous pouvez instancier. Vous devrez peut-être utiliser des wrappers newtype (comme Flip ci-dessus) pour placer les arguments où ils doivent être qualifiés pour construire une instance d'un autre type de classe. C'est le prix que nous payons pour l'inférence des contraintes de classe de type.

Questions connexes