You may ask yourself what type of this generalized comparison function is. First of all, we need a way to encode component types:
data Tuple ??? = Nil | Cons a (Tuple ???)
There is really nothing real, we can replace the question marks. The conclusion is that regular ADT is not enough, so we need our first language extension, GADTs:
data Tuple :: ??? -> * where Nil :: Tuple ??? Cons :: a -> Tuple ??? -> Tuple ???
But we end with question marks. Two more extensions are required to fill the holes: DataKinds and TypeOperators:
data Tuple :: [*] -> * where Nil :: Tuple '[] Cons :: a -> Tuple as -> Tuple (a ': as)
As you can see, we need three types of system extensions for type encoding only. Can we compare now? Well, it is not so easy to answer, because in fact it is far from obvious how to write an autonomous comparison function. Fortunately, a class type mechanism allows us to use a simple recursive approach. However, this time we are not just returning to the level of values, but also to the type of level. Obviously, empty tuples are always equal:
instance Eq (Tuple '[]) where _ == _ = True
But the compiler complains again. What for? We need another FlexibleInstances extension, because '[] is a specific type. Now we can compare empty tuples, which is not so important. What about non-empty tuples? We need to compare the heads as well as the rest of the tuple:
instance (Eq a, Eq (Tuple as)) => Eq (Tuple (a ': as)) where Cons x xs == Cons y ys = x == y && xs == ys
It seems to make sense, but boom! We get another complaint. Now the compiler wants FlexibleContexts because we have a not completely polymorphic type in context, Tuple as .
A total of five system extensions to the system, three of which are simply for expressing a tuple type, and they did not exist before GHC 7.4. The remaining two are necessary for comparison. Of course, there is a gain. We get a very powerful type of tuple, but because of all these extensions, we obviously cannot put that type of tuple in the base library.