2011-07-19 3 views
9

Tenez compte du type de classe suivante:instances de type classe pour les types avec 2 paramètres lorsque la classe de type a un seul

class Listable a where 
    asList :: a t -> [t] 

Il est assez facile de créer des instances pour les types avec un seul paramètre:

instance Listable [] where 
    asList = id 

instance Listable Maybe where 
    asList (Just x) = [x] 
    asList Nothing = [] 

Maintenant comment est-ce que je ferais une instance pour une paire avec deux paramètres de type identiques? Bien sûr que je pouvais faire un peu d'emballage:

data V2 a = V2 a a 

v2 (p,q) = V2 p q 

instance Listable V2 where 
    asList (V2 p q) = [p,q] 

Maintenant, je pourrais écrire des choses comme asList $ v2 (47, 11), mais ce genre de défaites le but.

Existe-t-il un moyen de limiter le type de paire aux cas où les deux paramètres de type sont égaux, et d'écrire une instance Listable pour cela? Si non, quelle est la solution de contournement habituelle?

Répondre

13

Il y a beaucoup de façons de le faire, sur le plan conceptuel. Malheureusement, la plupart d'entre eux ne fonctionnent pas réellement. Hélas!

Tout d'abord, en tant que programmeur fonctionnel, je parierais que c'est ce que vous vraiment voulez écrire:

instance Listable (\a -> (a, a)) where 
    asList (p, q) = [p,q] 

Malheureusement lambdas niveau de type n'existent pas. Nous pourrions écrire une version nommée du lambda ci-dessus en utilisant un synonyme de type:

type Same2 f a = f a a 

instance Listable (Same2 (,)) where { ... } 

Ce n'est pas permis non plus, parce que le synonyme de type n'est pas entièrement appliquée. On pourrait plutôt imaginer la classe de type prenant un argument supplémentaire qui décrit comment appliquer les variables de type:

class Listable app f where 
    asList :: app f a -> [a] 

instance Listable __ Maybe where { ... } 

instance Listable __ (,) where { ... } 

Sans même penser à ce que app peut-être, cela ne fonctionne pas parce que nous n'avons pas une sorte cohérente pour le paramètre f. En passant à des choses qui fonctionnent réellement, je pense que la manière la plus courante est d'envelopper l'approche de type synonyme dans un newtype, puis de traiter simplement de l'emballage et du déballage que cela implique.

newtype Same2 f a = Same2 (f a a) 

instance Listable (Same2 (,)) where { ... } 

C'est faisable, même si c'est un peu moche. Vous pouvez également définir la composition du constructeur de type et d'autres jouets de cette façon, puis devenir fou avec des expressions sans point de niveau de type enfouies sous un tas de passe-partout.

En approche finale, vous pouvez également encoder l'approche de type lambda ci-dessus « en sens inverse », allant de la version entièrement appliquée, - le paramètre de type simple:

class Listable t where 
    type ListableElem t :: * 
    asList :: t -> [ListableElem t] 

être capable de faire ce genre de chose est l'une des principales motivations pour les familles de type. La même chose peut être exprimée avec MPTCs et fundeps, mais c'est 1) équivalent et 2) beaucoup plus laid, donc je ne vais pas prendre la peine de l'écrire.

+0

Désolé d'être paresseux pour l'essayer, mais la dernière approche permettrait 'instance Listable (a, a)' - n'est-ce pas? – yatima2975

+0

@ yatima2975: Au lieu d'avoir différentes instances avec '(,)' comme constructeur de type externe, oui. Probablement aurait dû mettre cela comme un exemple dans ma réponse, maintenant que vous le mentionnez ... –

2

Vous devez définir un type de wrapper pour cela. (Vous devez toutefois utiliser newtype.) Vous pouvez même définir:

newtype Foo t a = Foo(t a a) 

instance Listable (Foo (,)) where 
    asList (Foo (a,b)) = [a,b] 
Questions connexes