The hint is right there in the error message:
N.B.: ‘State’ is a type function, and may not be injective
What this means: an injective function f is a function where from f(x) = f(y) it follows that x = y. Now, we're talking type-level here, so if State
were injective, it would follow from State p ~ State q
that p ~ q
.
In s == goal
, the compiler knows it needs to unify goal
with s
(because ==
always compares values of the same type), so there we have it:
s :: State p
goal :: State q
State p ~ State q
but because State
is not injective, the compiler can't infer that p ~ q
, i.e. that we're only talking about a single instance of the type class.
Why not? Well, you could come up with:
instance Problem Int where
type State Int = Bool
goal = True
instance Problem Double where
type State Double = Bool
goal = False
Now we have State Int ~ State Double
. Yet obviously Int
and Double
aren't the same type, and they define goal
in contradictory ways.
“How to fix this” – well, you need to redesign the class.
You can use
class Problem p where
data State p :: *
In this case, State
is injective, because every instantiation needs to be hard-baked into a single instance Problem
.
If you need the ability to define the actual State
types elsewhere, you need to give the compiler an explicit hint which p
should be used for goal
. The usual solution are proxies or – preferrable IMO – tagged values:
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Tagged
class Problem p where
type State p :: *
goal :: Eq (State p) => Tagged p (State p)
goal = Tagged undefined
isGoal :: Eq (State p) => Tagged p (State p) -> Bool
isGoal = isGoalDef
isGoalDef :: forall p . Eq (State p) => Tagged p (State p) -> Bool
isGoalDef (Tagged s) = s == g
where (Tagged g) = goal :: Tagged p (State p)
goal
from the same instance. – melpomene