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 overloadedx
's you mean. For example, ifread
is an overloaded function that takes strings to integers or Booleans, andshow
is an overloaded function that takes integers or Booleans to strings, then the expressionshow (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)