6.2. Type class extensions

6.2.1. More flexible contexts

In Haskell 98, contexts consist of class constraints on type variables applied to zero or more types, as in

f :: (Functor f, Num (f Int)) => f String -> f Int -> f Int

In class and instance declarations only type variables may be constrained. With the -98 option, any type may be constrained by a class, as in

g :: (C [a], D (a -> b)) => [a] -> b

Classes are not limited to a single argument either (see Section 6.2.4).

6.2.2. More flexible instance declarations

In Haskell 98, instances may only be declared for a data or newtype type constructor applied to type variables. With the -98 option, any type may be made an instance:

instance Monoid (a -> a) where ...
instance Show (Tree Int) where ...
instance MyClass a where ...
instance C String where

This relaxation, together with the relaxation of contexts mentioned above, makes the checking of constraints undecidable in general (because you can now code arbitrary Prolog programs using instances). To ensure that type checking terminates, Hugs imposes a limit on the depth of constraints it will check, and type checking fails if this limit is reached. You can raise the limit with the -c option, but such a failure usually indicates that the type checker wasn't going to terminate for the particular constraint problem you set it.

Note that GHC implements a different solution, placing syntactic restrictions on instances to ensure termination, though you can also turn these off, in which case a depth limit like that in Hugs is used.

6.2.3. Overlapping instances

With the relaxation on the form of instances discussed in the previous section, it seems we could write

class C a where c :: a
instance C (Bool,a) where ...
instance C (a,Char) where ...

but then in the expression c :: (Bool,Char), either instance could be chosen. For this reason, overlapping instances are forbidden:

ERROR "Test.hs":4 - Overlapping instances for class "C"
*** This instance   : C (a,Char)
*** Overlaps with   : C (Bool,a)
*** Common instance : C (Bool,Char)

However if the +o option is set, they are permitted when one of the types is a substitution instance of the other (but not equivalent to it), as in

class C a where toString :: a -> String
instance C [Char] where ...
instance C a => C [a] where ...

Now for the type [Char], the first instance is used; for any type [t], where t is a type distinct from Char, the second instance is used. Note that the context plays no part in the acceptability of the instances, or in the choice of which to use.

The above analysis omitted one case, where the type t is a type variable, as in

f :: C a => [a] -> String
f xs = toString xs

We cannot decide which instance to choose, so Hugs rejects this definition. However if the +O option is set, this declaration is accepted, and the more general instance is selected, even though this will be the wrong choice if f is later applied to a list of Char.

Hugs used to have a +m option (for multi-instance resolution, if Hugs was compiled with MULTI_INST set), which accepted more overlapping instances by deferring the choice between them, but it is currently broken.

Sometimes one can avoid overlapping instances. The particular example discussed above is similar to the situation described by the Show class in the Prelude. However there overlapping instances are avoided by adding the method showList to the class.

6.2.4. Multiple parameter type classes

In Haskell 98, type classes have a single parameter; they may be thought of as sets of types. In Hugs, they may have one or more parameters, corresponding to relations between types, e.g.

class Isomorphic a b where
    from :: a -> b
    to :: b -> a

6.2.5. Functional dependencies

Multiple parameter type classes often lead to ambiguity. Functional dependencies (inspired by relational databases) provide a partial solution, and were introduced in “Type Classes with Functional Dependencies”, Mark P. Jones, In Proceedings of the 9th European Symposium on Programming, LNCS vol. 1782, Springer 2000.

Functional dependencies are introduced by a vertical bar:

class MyClass a b c | a -> b where

This says that the b parameter is determined by the a parameter; there cannot be two instances of MyClass with the same first parameter and different second parameters. The type inference system then uses this information to resolve many ambiguities. You can have several dependencies:

class MyClass a b c | a -> b, a -> c where

This example could also be written

class MyClass a b c | a -> b c where

Similarly more than one type parameter may appear to the left of the arrow:

class MyClass a b c | a b -> c where

This says that the c parameter is determined by the a and b parameters together; there cannot be two instances of MyClass with the same first and second parameters, but different third parameters.