Search code examples
haskellghci

How to extend a Haskell type instance?


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?


Solution

  • 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!