2010-11-23 4 views
3

Disons que j'ai trois constructeurs de valeur:constructeur une valeur appartenant à deux types différents

A { a :: Int } 
B { b :: Char } 
C { c :: Bool } 

Je voudrais créer deux types X et Y telle qu'une valeur de type X peut être un A, B ou C, quelque chose comme ceci:

data X = A {...} | B {...} | C {...} 

et une valeur de type Y ne peut être qu'un A ou B, quelque chose comme ceci:

data Y = A {...} | B {...} 

pour que je puisse coder quelque chose comme ceci:

foo :: X -> Int -- can pattern match 
foo (A _) = 1 
foo (B _) = 2 
foo (C _) = 3 
bar :: Y -> Bool -- also can pattern match with the same constructors 
bar (A _) = true 
bar (B _) = false 
baz = A 1 -- baz is inferred to be a type that can fit in both X and Y 

Je sais que je peux envelopper les constructeurs dans les définitions de X et Y comme celui-ci:

data X = XA A | XB B | XC C 
data Y = YA A | YB B 

mais cela semble désordonné (devoir taper XA A etc tout le temps). Je pourrais étendre le contenu de A, B, et C dans les définitions de X et Y, mais A etc. sont assez compliqués et je préférerais ne pas dupliquer la définition. Est-ce possible avec Haskell, y compris les extensions GHC?

Modifier

Il semble que GADTs peut répondre à ma question posée (donc je marqué la réponse du radiateur comme correct), mais ne sont pas assez souples pour ce que je dois. Par exemple, pour autant que je sache, vous ne pouvez pas faire quelque chose comme:

func1 :: [XY Y_] -- returns a list of items that can only be A or B 
func1 = ... 
func2 = func1 ++ [C True] -- adding a C item to the list 

func2 doivent être dactylographiés comme [XY X_], mais cela est impossible dans Haskell (à moins que mon expérience a eu tort). Après plus de recherches sur le web, ce que je veux vraiment, ce sont les variantes polymorphes d'OCaml qui (pour autant que je sache) n'existent qu'en OCaml (en regardant les langages plus «pratiques» que «académiques»).

Modifier 2

Voir la réponse de comonad. Il semble que cela peut vraiment être fait, mais je pense que je ferais mieux de ne pas réécrire cette question trop de fois. :-)

+1

Vos définitions de 'foo' et' bar' ne taperont pas check, parce que 'A _' est par définition une valeur de type' A', pas 'X' ou' Y'. Vous ne pouvez pas avoir un autre type ('X') avec le même constructeur. – sastanin

+0

Une autre façon de le regarder: quel type A aurait? Il ne peut en avoir qu'un, donc il ne peut pas être 'Int -> X' et' Int -> Y' simultanément. –

+0

jetxee et Antal S-Z: Je sais que mes définitions de 'foo' et' bar' ne seront pas typecheck, mais j'espérais que quelque chose de similaire serait valide Haskell. Par exemple, avec typeclasses, 'read x' (où' x' est un 'String') peut avoir (dans un sens) plus d'un type. – yondalf

Répondre

7

Les classes de type jetxee décrites sont probablement l'approche appropriée.

Si vous souhaitez également pouvoir modéliser et utiliser des constructeurs, vous pouvez définir tous les constructeurs dans un type de données à l'aide de GADT et de déclarations de données vides. Si vous prenez cette approche, tous les constructeurs seront membres du même type de données, tout en vous permettant de restreindre le domaine à seulement un sous-ensemble des constructeurs.

data X_ 
data Y_ 
data XY a where 
    A :: Int -> XY a 
    B :: Char -> XY a 
    C :: Bool -> XY X_ 
type X = XY X_ -- Contains values built with constructors A, B, and C 
type Y = XY Y_ -- Contains only values built with constructors A and B 

maintenant une fonction qui utilise uniquement A et B fonctionne avec les deux types X et Y. Une fonction qui utilise C fonctionnera uniquement avec le type X.

+0

Merci. Je vais jeter un coup d'œil aux GADT et voir s'ils sont ce dont j'ai besoin. – yondalf

+0

Voir la fiche de ma question – yondalf

5
baz = A 1 -- baz is inferred to be a type that can fit in both X and Y 

Cela nécessiterait que Haskell prenne en charge une forme de sous-typage, ce qui n'est pas le cas. Il n'y a pas d'extensions ghc qui permettent cela non plus.

La meilleure chose que vous pouvez faire est probablement quelque chose comme ceci:

data Y = A ... | B ... 
data X = XY Y | C ... 

De cette façon, vous n'avez pas à répéter les constructeurs A et B et vous ne pas écrire Y (A foo) soit - vous pouvez il suffit d'écrire A foo pour obtenir une valeur de type Y.

Cependant, vous devrez écrire X (A foo) pour obtenir une valeur de type X qui contient un A. Ce n'est pas tout à fait ce que tu veux, mais je crains que tu ne te rapproches le plus possible.

+0

En effet, par "définition" un objet comme un et un seul "type" par opposition à la classe.Ou si vous préférez les jeux de caractères sont une partition de l'ensemble de tous les "objets" dans Haskell – mb14

+0

Hmm ... ouais je suppose que j'espérais quelque chose de mieux que cela. Peut-être que GADTs (une autre réponse suggérée) serait ce que je cherche. – yondalf

2

Vos définitions pour foo et bar ne seront pas de type chèque, car A _ est par définition une valeur de type A, non X ou Y. Vous ne pouvez pas avoir un autre type (X) avec le même constructeur. Donc, la bonne chose est ce que vous avez écrit:

data X = XA A | XB B | XC C 
data Y = YA A | YB B 

Mais approchons-la d'un autre point de vue. Pourquoi en avez-vous besoin? Vous voulez exprimer, que X peut être soit A, B ou C et Y peut être A ou B. Vous ne vous souciez pas des valeurs de A, B et C respectivement. Donc étant A et étant B sont des caractéristiques communes aux deux X et Y.

Lorsque vous avez un trait commun beteen deux types (X et Y dans ce cas), vous pouvez souvent exprimer avec des classes de type. Veuillez noter que les classes de types sont ouvertes, de sorte que de nombreux types peuvent les implémenter comme vous le souhaitez.

Par exemple, on peut définir trois classes de type qui permettent de vérifier si le type a A, B ou C:

class HasA t where hasA :: t -> Bool 
class HasB t where hasB :: t -> Bool 
class HasC t where hasC :: t -> Bool 

Maintenant, pour nos types nous avons encore utiliser des constructeurs de données distincts:

data A = A Int 
data B = B Char 
data C = C Bool 
data X = XA A | XB B | XC C 
data Y = YA A | YB B 

Mais nous pouvons définir des instances de classe à la fois pour X et pour Y:

instance HasA X where 
    hasA (XA _) = True 
    hasA _ = False 
instance HasB X where 
    hasB (XB _) = True 
    hasB _ = False 
instance HasC X where 
    hasC (XC _) = True 
    hasC _ = False 

instance HasA Y where 
    hasA (YA _) = True 
    hasA _ = False 
instance HasB Y where 
    hasB (YB _) = True 
    hasB _ = False 
instance HasC Y where 
    hasC = const False 

Avec ces classes de type, vous pouvez écrire foo et bar qui acceptent à la fois X s et Y s.

foo :: (HasA t, HasB t, HasC t) => t -> Int 
foo v | hasA v = 1 
     | hasB v = 2 
     | hasC v = 3 
     | otherwise = undefined 
bar :: (HasA t, HasB t) => t -> Bool 
bar v | hasA v = True 
     | hasB v = False 
     | otherwise = undefined 

xs = [ XA (A 1), XB (B '1'), XC (C True) ] 
ys = [ YA (A 1), YB (B '1') ] 

Plus précisément, foo accepte quoi que ce soit qui implémente HasA, HasB et HasC, et bar accepte quoi que ce soit qui met en œuvre HasA et HasB (ou non HasC est mis en œuvre est dénué de pertinence dans le contexte de bar). Si la mise en œuvre arrive à retourner False dans tous les cas, alors foo et bar sont indéfinis.

Par exemple:

ghci> map foo xs 
[1,2,3] 
ghci> map foo ys 
[1,2] 
ghci> map bar xs 
[True,False,*** Exception: Prelude.undefined 
ghci> map bar ys 
[True,False] 

S'il vous plaît noter que bar accepte également X s, mais il est indéfini si elle se trouve être quelque chose d'autre, sauf A ou B. Vous en tant que programmeur, pas le compilateur, êtes responsable de penser à l'exhaustivité des gardes dans ce cas.

Si vous avez également besoin des valeurs A, B et C, vous devez définir différemment les classes de type, par ex. comme

class HasA t where getA :: t -> Maybe A 

mais l'idée est la même.

+0

Merci. J'ai pensé à cela mais je pensais que je ne pouvais pas faire de correspondance entre les différents types (XA, YA) qui satisfont la classe de caractères (HasA), mais je ne pensais pas aux gardes. Cependant, j'ai besoin des valeurs de A, B et C, et probablement cette solution serait aussi verbeuse (sinon plus) que l'idée originale XA et YA. – yondalf

1

En utilisant la réponse de Dissipateur, je Kame avec ceci:

{-# LANGUAGE GADTs,EmptyDataDecls #-} 
module Test where 


data NotThatY 
data XY a where 
    A :: Int -> XY a 
    B :: Char -> XY a 
    C :: Bool -> XY NotThatY 

type Y a = XY a 
type X a = XY NotThatY -- or type X =..., but (X a) looks better alongside (Y a). 

func1 :: [Y a] 
func1 = [A 5, B 'ö'] 
func2 :: [X a] 
func2 = func1 ++ [C True] 

La restriction à Y est supprimé. Maintenant, cela fonctionne, mais il semble en quelque sorte étrange avec ce un dans le type.

type Y = forall a. XY a - ne fonctionne pas.

+0

Hmm ... alors ça peut être fait. :-) – yondalf

Questions connexes