Haskell family instance with type restrictions

I am trying to imagine expressions with type families, but I cannot figure out how to write the constraints that I want, and I am starting to feel that this is simply not possible. Here is my code:

class Evaluable c where type Return c :: * evaluate :: c -> Return c data Negate n = Negate n instance (Evaluable n, Return n ~ Int) => Evaluable (Negate n) where type Return (Negate n) = Return n evaluate (Negate n) = negate (evaluate n) 

All of this compiles fine, but it does not express exactly what I want. In the limitations of the Negate Evaluable I say that the return type of the expression inside Negate must be Int (with Return n ~ Int ), so I can call it negation, but this is too restrictive. The return type should actually be an instance of a class of type Num that has a Negate function. Thus, Double s, Integer s, or any other Num instance could also be undone, not just Int s. But I can’t just write

 Return n ~ Num 

because Num is a type class, and Return n is a type. I also can not put

 Num (Return n) 

because Return n is a type, not a type variable.

Am I trying to do this with Haskell? If not, if so, or do I not understand some theory behind it? I feel that Java can add a restriction similar to this. Let me know if this question can be clearer.

Edit: Thanks guys, the answers help and get what I suspected. It seems like the type checker cannot handle what I would like to do without UndecidableInstances, so my question is what I would like to express really insoluble? This is for the Haskell compiler, but is it at all? that is, there may be a constraint that means "check that Return n is an instance of Num" that is solvable for a more advanced type of check?

+7
haskell typeclass type-constraints type-families type-level-computation
source share
2 answers

Actually, you can do what you mentioned:

 {-# LANGUAGE TypeFamilies, FlexibleContexts, UndecidableInstances #-} class Evaluable c where type Return c :: * evaluate :: c -> Return c data Negate n = Negate n instance (Evaluable n, Num (Return n)) => Evaluable (Negate n) where type Return (Negate n) = Return n evaluate (Negate n) = negate (evaluate n) 

Return n , of course, is a type that can be an instance of a class similar to Int . Your confusion may be about what might be an argument to the constraint. The answer is "something with the right look." Type Int is * , as is Type Return n . Num has the form * -> Constraint , so any good of it * can be its argument. It is completely legal (albeit empty) to write Num Int as a restriction, just as Num (a :: *) is legal.

+6
source share

To complement Eric's answer, let me suggest one possible alternative: using a functional dependency instead of a type family:

 class EvaluableFD rc | c -> r where evaluate :: c -> r data Negate n = Negate n instance (EvaluableFD rn, Num r) => EvaluableFD r (Negate n) where evaluate (Negate n) = negate (evaluate n) 

This makes talking about the type of result a bit easier, I think. For example, you can write

 foo :: EvaluableFD Int a => Negate a -> Int foo x = evaluate x + 12 

You can also use ConstraintKinds to apply this in part (which is why I put the arguments in this ridiculous order):

 type GivesInt = EvaluableFD Int 

You could do this with your class as well, but that would be more annoying:

 type GivesInt x = (Evaluable x, Result x ~ Int) 
+2
source share

All Articles