bluespec-docs/content/chapter2/page1.md
2025-02-12 15:54:12 -05:00

4.1 KiB

+++ title = "Type classes and overloading" weight = 1 +++

BH's class and instance mechanisms form a systematic way to do overloading (the approach has been well tested in Haskell).

Overloading is a way to use a common name to refer to a set of operations at different types. For example, we may want to use the "<" operator name for the integer comparison operation, the floating-point comparison operation, the vector comparison operation and the matrix comparison operation. Note that this is not the same as polymorphism: a polymorphic function is a single function that is meaningful at an infinity of types (i.e., at every possible instantiation of the type variables in its type). An overloaded identifier, on the other hand, usually uses a common name to refer to a (usually) small set of distinct operations.

Further, it may make sense to have "<=", ">" and ">=" operations wherever there is a "<" operation, on integers, floating points numbers, vectors and matrices. Rather than handle these separately, we say:

  • there is class of types which we will call Ord (for "ordered types"),
  • that the integer, floating point, vector and matrix types are members (or "instances") of this class, and
  • that all types that are members of this class have appropriate definitions for the "<", "<=", ">" and ">=" operations. We also say that these operations are overloaded across these instance types, and we refer to these operations as the methods of this class.

Another example: we could use a class Hashable with an operation called hash to represent those types T for which we can and do define a hashing function. Each such type T has to specify how to compute the hash function at that type.

Classes, and the membership of a type in a class, do not come into existence by magic. Every class is created explicitly using a class declaration, described in section 4.5. A type must explicitly be made an instance of a class and the corresponding class methods have to be provided explicitly; this is described in 4.6.

Context-qualified types

Consider the following type declaration:

sort :: (Ord a) => List a -> List a

It expresses the idea that a sorting function takes an (unsorted) input list of items and produces a (sorted) output list of items, but it is only meaningful for those types of items ("a") for which the ordering functions (such as "<") are defined. Thus, it is ok to apply sort to lists of Integer's or lists of Bool's, because those types are instances of Ord, but it is not ok to apply sort to a list of, say, Counter's (assuming Counter is not an instance of the Ord class).

In the type of sort above, the part before "=>" is called a context. A context expresses constraints on one or more type variables--- in the above example, the constraint is that any actual type "a" must be an instance of the Ord class.

A context-qualified type has the following grammar:

ctxType ::= [ context => ] type
context ::= ( {classId { varId }, })
classId ::= conId

In the above example, the class Ord had only one type parameter (i.e., it constrains a single type) but, in general, a type class can have multiple type parameters. For example, in BH we frequently use the class "Bits a n" which constrains the type represented by a to be representable in bit strings of length represented by the type n.

NOTE:

When using an overloaded identifier x there is always a question of whether or not there is enough type information available to the compiler to determine which of the overloaded x's you mean. For example, if read is an overloaded function that takes strings to integers or Booleans, and show is an overloaded function that takes integers or Booleans to strings, then the expression show (read s) is ambiguous--- is the thing to be read an integer or a Boolean?

In such ambiguous situations, the compiler will so notify you, and you may need to give it a little help by inserting an explicit type signature, e.g.,

show ((read s) :: Bool)