6.8.4. Default method signatures


Allows the definition of default method signatures in class definitions.

Haskell 98 allows you to define a default implementation when declaring a class:

class Enum a where
  enum :: [a]
  enum = []

The type of the enum method is [a], and this is also the type of the default method. You can change the type of the default method by requiring a different context using the extension DefaultSignatures. For instance, if you have written a generic implementation of enumeration in a class GEnum with method genum, you can specify a default method that uses that generic implementation. But your default implementation can only be used if the constraints are satisfied, therefore you need to change the type of the default method

class Enum a where
  enum :: [a]
  default enum :: (Generic a, GEnum (Rep a)) => [a]
  enum = map to genum

We reuse the keyword default to signal that a signature applies to the default method only; when defining instances of the Enum class, the original type [a] of enum still applies. When giving an empty instance, however, the default implementation (map to genum) is filled-in, and type-checked with the type (Generic a, GEnum (Rep a)) => [a].

The type signature for a default method of a type class must take on the same form as the corresponding main method’s type signature. Otherwise, the typechecker will reject that class’s definition. By “take on the same form”, we mean that the default type signature should differ from the main type signature only in their outermost contexts. Therefore, if you have a method bar:

class Foo a where
  bar :: forall b. C => a -> b -> b

Then a default method for bar must take on the form:

default bar :: forall b. C' => a -> b -> b
bar = ...

C is allowed to be different from C', but the right-hand sides of the type signatures must coincide. We require this because when you declare an empty instance for a class that uses DefaultSignatures, GHC implicitly fills in the default implementation like this:

instance Foo Int where
  bar = default_bar

Where default_bar is a top-level function based on the default type signature and implementation for bar:

default_bar :: forall a b. (Foo a, C') => a -> b -> b
default_bar = ...

In order for this approach to work, the default type signature for bar should be the same as the non-default signature, modulo the outermost context (with some caveats—see Detailed requirements for default type signatures). There is no obligation for C and C' to be the same, and indeed, the Enum example above relies on enum‘s default type signature having a more specific context than the original type signature.

We use default signatures to simplify generic programming in GHC (Generic programming).

6.8.5. Detailed requirements for default type signatures

The rest of this section gives further details about what constitutes valid default type signatures.

  • Ignoring outermost contexts, a default type signature must match the original type signature according to GHC’s subsumption rules. As a result, the order of type variables in the default signature is important. Recall the Foo example from the previous section:

    class Foo a where
      bar :: forall b. C => a -> b -> b
      default bar :: forall b. C' => a -> b -> b
      bar = ...

    This is legal because if you remove the outermost contexts C and C', then the two type signatures are the same. It is not necessarily the case that the default signature has to be exactly the same, however. For instance, this would also be an acceptable default type signature, as it is alpha-equivalent to the original type signature:

    default bar :: forall x. C' => a -> x -> x

    On the other hand, this is not an acceptable default type signature, since the type variable a is in the wrong place:

    default bar :: forall b. C' => b -> a -> b
  • The only place where a default type signature is allowed to more precise than the original type signature is in the outermost context. For example, this would not be an acceptable default type signature, since we can’t match the type variable b with the concrete type Int:

    default bar :: C' => a -> Int -> Int

    You can, however, use type equalities to achieve the same result:

    default bar :: forall b. (C', b ~ Int) => a -> b -> b
  • Because of GHC’s subsumption rules rules, there are relatively tight restrictions concerning nested or higher-rank foralls (see Arbitrary-rank polymorphism). Consider this class:

    class C x where
      m :: x -> forall a b. a -> b

    GHC would not permit the following default type signature for m:

    default m :: x -> forall b a. a -> b

    This is because the default signature quantifies the nested foralls in a different order than the original type signature. In order for this to typecheck, the default signature must preserve the original order:

    default m :: x -> forall a b. a -> b

    Note that unlike nested or higher-rank foralls, outermost foralls have more flexibility in how they are ordered. As a result, GHC will permit the following:

    class C' x where
      m'         :: forall a b. x -> a -> b
      default m' :: forall b a. x -> a -> b
      m' = ...
  • Just as the order of nested or higher-rank foralls is restricted, a similar restriction applies to the order in which nested or higher-rank contexts appear. As a result, GHC will not permit the following:

    class D a where
      n         :: a -> forall b. (Eq b, Show b) => b -> String
      default n :: a -> forall b. (Show b, Eq b) => b -> String
      n = ...

    GHC will permit reordering constraints within an outermost context, however, as demonstrated by the fact that GHC accepts the following:

    class D' a where
      n'         :: (Eq b, Show b) => a -> b -> String
      default n' :: (Show b, Eq b) => a -> b -> String
      n' = ...
  • Because a default signature is only ever allowed to differ from its original type signature in the outermost context, not in nested or higher-rank contexts, there are certain defaults that cannot be written without reordering forall s. Consider this example:

    class E a where
      p :: Int -> forall b. b -> String

    Suppose one wishes to write a default signature for p where the context must mention both a and b. While the natural thing to do would be to write this default:

    default p :: Int -> forall b. DefaultClass a b => b -> String

    This will not typecheck, since the default type signature now differs from the original type signature in its use of nested contexts. The only way to make such a default signature work is to change the order in which b is quantified:

    default p :: forall b. DefaultClass a b => Int -> b -> String

    This works, but at the expense of changing p‘s behavior with respect to Visible type application.