I canโt think of a way to literally implement either for Constraint s, unfortunately, but if we just combine the equalities, as in your example, we can tweak your approach to the class of classes and make it closed by type families and raised Booleans. This will only work in GHC 7.6 and above; at the end, I mentioned how it would be better in GHC 7.8 and how to do it in GHC 7.4.
The idea is this: just as we could declare a value level function isBananaColor :: Color -> Bool , we can also declare a level function like isBananaColor :: Color -> Bool :
type family IsBananaColor (c :: Color) :: Bool type instance IsBananaColor Green = True type instance IsBananaColor Yellow = True type instance IsBananaColor Black = True type instance IsBananaColor White = False type instance IsBananaColor Red = False type instance IsBananaColor Blue = False type instance IsBananaColor Tawny = False type instance IsBananaColor Purple = False
If we like, we can even add
type BananaColor c = IsBananaColor c ~ True
Then we repeat this for each color of the fruit and define Fruit as in your second example:
{-
(If you want, you can get rid of the -XConstraintKinds and type XYZColor c = IsXYZColor c ~ True types and simply define Fruit constructors as XYZ :: IsXYZColor c ~ True => Fruit c .)
Now, what are you buying, and that he is not buying you? On the positive side, you get the opportunity to determine your type the way you want, which is definitely a victory; and since Color closed, no one can add more instances of the instance family and violate this.
However, there are also disadvantages. You will not get the conclusion you want to tell you automatically that [Apple, Grape, Banana] is of type Fruit Green ; what's worse is that [Apple, Grape, Banana] is a valid type (AppleColor c, GrapeColor c, BananaColor c) => [Fruit c] . Yes, there is no way to monomorphize this, but the GHC cannot understand this. To be completely honest, I canโt imagine what solution these properties will give you, although Iโm always ready to be surprised. Another obvious problem with this solution is how long it takes: you need to define all eight color cases for each IsXYZColor type IsXYZColor ! (Using a new type of family for each is also annoying, but inevitable with decisions of this form.)
I mentioned above that GHC 7.8 will do it better; this will be done by eliminating the need to list each individual case for each individual IsXYZColor class. How? Well, Richard Eisenberg et al. Introduced closed overlapping ordered type families in the GHC HEAD, and it will be available in 7.8. There's a document entitled POPL 2014 (and an extended version ) on this topic, and Richard also wrote an introductory blog post (looks like outdated syntax).
The idea is to allow type instances of types to be declared as ordinary functions: all equations must be declared in one place (excluding the open world assumption) and checked in order, which allows overlapping. Sort of
type family IsBananaColor (c :: Color) :: Bool type instance IsBananaColor Green = True type instance IsBananaColor Yellow = True type instance IsBananaColor Black = True type instance IsBananaColor c = False
ambiguous, because IsBananaColor Green corresponds to both the first and last equations; but in a normal function, it will work fine. So the new syntax is:
type family IsBananaColor (c :: Color) :: Bool where IsBananaColor Green = True IsBananaColor Yellow = True IsBananaColor Black = True IsBananaColor c = False
This type family ... where { ... } block defines a type family the way you want to define it; it signals that this type family is closed, ordered, and overlapping, as described above. Thus, the code in GHC 7.8 will look like this: (unverified since it is not installed on my machine):
{-
Hooray, we can read it without falling asleep from boredom! In fact, you will notice that I switched to the explicit version of IsXYZColor c ~ True for this code; I did this because, because the template for the additional four synonyms became much more obvious and annoying with these shorter definitions!
However, release in the opposite direction and make this code more ugly. What for? Well, GHC 7.4 (which, alas, I still have on my machine) does not support type families with a result type not * . What can we do instead? We can use type classes and functional dependencies to fake. The idea is that instead of isBananaColor :: Color -> Bool we have a class like IsBananaColor :: Color -> Bool -> Constraint , and we add a functional color dependency to a Boolean. Then IsBananaColor cb doable if and only if IsBananaColor c ~ b in a more pleasant version; because Color is closed, and we have a functional dependence on it, it still gives us the same properties that it is just more ugly (although mostly conceptual). Without further ado, full code:
{-