This excellent tutorial, defines a YesNo
typeclass and a YesNo Int
instance as following:
class YesNo a where
yesno :: a -> Bool
instance YesNo Int where
yesno 0 = False
yesno _ = True
This works fine with an explicit type declaration like:
*Main> yesno (0::Int)
False
I want to avoid the explicit type declaration and make it work with Num a
class constraint:
instance (Num a) => YesNo a where
yesno 0 = False
yesno _ = True
But this instance definition does not compile with the error:
Constraint is no smaller than the instance head
I understand that Num
has a larger number of constructors than the YesNo
class and hence the error. But how do I fix this without setting the UndecidableInstances
compiler flag?
As chi already remarked, -XUndecidableInstances
is not that much of a problem – alright, it's more intrusive than, say, -XFlexibleInstances
; you should think for a moment whenever this is needed – but it's perfectly safe. It will never succeed in compiling code that doesn't work the intended way.
That said, to achieve the goal of allowing yesno 0
to work on its own, instance (Num a) -> YesNo a
is not the right thing. With that instance in place, it will still be totally ambiguous what concrete type the number 0
is supposed to have (Num a => a
is not a concrete type). Hence the compiler must jump in with defaulting and, well, the default Num
type is Integer
. Hence, to get this working it's completely sufficient to declare that instance:
instance YesNo Integer where
yesno = (/=0)
Alternatively, if you prefer the calculation to be always done in Int
, you can achieve that with an equational constraint:
instance (a ~ Int) => YesNo a where
yesno = (/=0)
This one does require -XUndecidableInstances
though (plus the totally harmless -XFlexibleInstances
and either of -XGADTs
or -XTypeFamilies
, to enable the equational constraint).
None of this makes much sense anyway: the point of a type class is to make instances for types whose implementation looks different. If you restrict this to number types, which is what you do with these instances, then you might as well stay away from type classes entirely and just define
yesno :: (Num n, Eq n) => n -> Bool
yesno = (/=0)
In that sense: undecidable instances are a bit of a code smell; before writing one make sure you actually need a class at all!