Real World Haskell has this example:
class BasicEq3 a where
isEqual3 :: a -> a -> Bool
isEqual3 x y = not (isNotEqual3 x y)
isNotEqual3 ::
No, I'm afraid GHC doesn't do anything like that. Also that isn't possible in general.
You see, the methods of a type class could be mutually recursive in a useful way. Here's a contrived example of such a type class. It's perfectly fine to not define either sumOdds
or sumEvens
, even though their default implementations are in terms of each other.
class Weird a where
measure :: a -> Int
sumOdds :: [a] -> Int
sumOdds [] = 0
sumOdds (_:xs) = sumEvens xs
sumEvens :: [a] -> Int
sumEvens [] = 0
sumEvens (x:xs) = measure x + sumOdds xs
I think it's perfectly fine for GHC to issue a warning in case of an "unbroken" cyclic dependency. There's even a ticket along those lines: http://hackage.haskell.org/trac/ghc/ticket/6028
Just because something is "undecidable" doesn't mean no instance of the problem can be solved effectively. GHC (or any other Haskell compiler) already has quite a bit of the information it needs, and it'd be perfectly possible for it to issue a warning if the user is instantiating a class without "breaking" the cyclic dependency. And if the compiler gets it wrong in the rare cases as exemplified in previous posts, then the user can have a -nowarnundefinedcyclicmethods
or a similar mechanism to tell GHC to be quiet. In nearly every other case, the warning will be most welcome and would add to programmer productivity; avoiding what's almost always a silly bug.
In my personal opinion, the defaulting mechanism is unnecessary and unwise. It would be easy for the class author to just provide the defaults as ordinary functions:
notEq3FromEq3 :: (a -> a -> Bool) -> (a -> a -> Bool)
notEq3FromEq3 eq3 = (\x y -> not (eq3 x y))
eq3FromNotEq3 :: (a -> a -> Bool) -> (a -> a -> Bool)
eq3FromNotEq3 ne3 = (\x y -> not (ne3 x y))
(In fact, these two definitions are equal, but that would not be true in general). Then an instance looks like:
instance BasicEq3 Bool where
isEqual3 True True = True
isEqual3 False False = True
isEqual3 _ _ = False
isNotEqual3 = notEq3FromEq3 isEqual3
and no defaults are required. Then GHC can warn you if you don't provide the definition, and any unpleasant loops have to be explicitly written by you into your code.
This does remove the neat ability to add new methods to a class with default definitions without affecting existing instances, but that's not so huge a benefit in my view. The above approach is also in principle more flexible: you could e.g. provide functions that allowed an Ord
instance to choose any comparison operator to implement.
No, there isn't, since if the compiler could make this determination, that would be equivalent to solving the Halting Problem. In general, the fact that two functions call each other in a "loop" pattern is not enough to conclude that actually calling one of the functions will result in a loop.
To use a (contrived) example,
collatzOdd :: Int -> Int
collatzOdd 1 = 1
collatzOdd n = let n' = 3*n+1 in if n' `mod` 2 == 0 then collatzEven n'
else collatzOdd n'
collatzEven :: Int -> Int
collatzEven n = let n' = n `div` 2 in if n' `mod` 2 == 0 then collatzEven n'
else collatzOdd n'
collatz :: Int -> Int
collatz n = if n `mod` 2 == 0 then collatzEven n else collatzOdd n
(This is of course not the most natural way to implement the Collatz conjecture, but it illustrates mutually recursive functions.)
Now collatzEven
and collatzOdd
depend on each other, but the Collatz conjecture states that calling collatz
terminates for all positive n
. If GHC could determine whether a class that had collatzOdd
and collatzEven
as default definitions had a complete definition or not, then GHC would be able to solve the Collatz conjecture! (This is of course not a proof of the undecideability of the Halting Problem, but it should illustrate why determining whether a mutually recursive set of functions is well-defined is not at all as trivial as it may seem.)
In general, since GHC cannot determine this automatically, documentation for Haskell classes will give the "minimal complete definition" necessary for creating an instance of a class.
I don't think so. I worry that you're expecting the compiler to solve the halting problem! Just because two functions are defined in terms of each other, doesn't mean it's a bad default class. Also, I've used classes in the past where I just needed to write instance MyClass MyType
to add useful functionality. So asking the compiler to warn you about that class is asking it to complain about other, valid code.
[Of course, use ghci during development and test every function after you've written it! Use HUnit and/or QuickCheck, just to make sure none of this stuff ends up in final code.]