Next: Abstract typeclass declarations, Previous: Typeclass declarations, Up: Type classes [Contents]

Once the interface of the type class
has been defined in the `typeclass`

declaration,
we can use an `instance`

declaration
to define how a particular type (or sequence of types)
satisfies the interface declared in the `typeclass`

declaration.

An instance declaration has the form

:- instanceclassname(typename(typevar, …), …) where [methoddefinition,methoddefinition, …].

An ‘`instance`’ declaration
gives a type for each parameter of the type class.
Each of these types must be either a type with no arguments,
or a polymorphic type whose arguments are all type variables.
For example `int`

, `list(T)`

,
`bintree(K, V)`

and `bintree(T, T)`

are allowed,
but `T`

and `list(int)`

are not.
The types in an instance declaration must not be abstract types
which are elsewhere defined as equivalence types.
A program may not contain
more than one instance declaration for a particular type
(or sequence of types, in the case of a multi-parameter type class)
and typeclass.
These restrictions ensure that there are no overlapping instance declarations,
i.e. for each typeclass there is at most one instance declaration
that may be applied to any type (or sequence of types).

There is no special interaction between subtypes and the typeclass system.
A subtype is *not* automatically an instance of a typeclass if
there is an ‘`instance`’ declaration for its supertype.

Each `methoddefinition` entry
in the ‘`where […]`’ part of an `instance`

declaration
defines the implementation of one of the class methods for this instance.
There are two ways of defining methods.
The first way is to define a method
by giving the name of the predicate or function which implements that method.
In this case, the `methoddefinition` must have one of the following forms:

pred(methodname/arity) isprednamefunc(methodname/arity) isfuncname

The `predname` or `funcname` must name
a predicate or function of the specified arity
whose type, modes, determinism, and purity are at least as permissive
as the declared type, modes, determinism, and purity
of the class method with the specified `methodname` and `arity`,
after the types of the arguments in the instance declaration
have been substituted in place of the parameters in the type class declaration.

The second way of defining methods is
by listing the clauses for the definition inside the instance declaration.
A `methoddefinition` can be a clause.
These clauses are just like the clauses
used to define ordinary predicates or functions (see Items),
and so they can be facts, rules, or DCG rules.
The only difference is that in instance declarations,
clauses are separated by commas rather than being terminated by periods,
and so rules and DCG rules in instance declarations
must normally be enclosed in parentheses.
As with ordinary predicates,
you can have more than one clause for each method.
The clauses must satisfy
the declared type, modes, determinism and purity for the method,
after the types of the arguments in the instance declaration
have been substituted in place of the parameters in the type class declaration.

These two ways are mutually exclusive:
each method must be defined either by a single naming definition
(using the ‘`pred(…) is predname`’
or ‘

Here is an example of an instance declaration and the different kinds of method definitions that it can contain:

:- typeclass foo(T) where [ func method1(T, T) = int, func method2(T) = int, pred method3(T::in, int::out) is det, pred method4(T::in, io.state::di, io.state::uo) is det, func method5(bool, T) = T ]. :- instance foo(int) where [ % method defined by naming the implementation func(method1/2) is (+), % method defined by a fact method2(X) = X + 1, % method defined by a rule (method3(X, Y) :- Y = X + 2), % method defined by a DCG rule (method4(X) --> io.print(X), io.nl), % method defined by multiple clauses method5(no, _) = 0, (method5(yes, X) = Y :- X + Y = 0) ].

Each ‘`instance`’ declaration
must define an implementation for every method
declared in the corresponding ‘`typeclass`’ declaration.
It is an error to define more than one implementation
for the same method within a single ‘`instance`’ declaration.

Any call to a method must have argument types (and in the case of functions, return type) which are constrained to be a member of that method’s type class, or which match one of the instance declarations visible at the point of the call. A method call will invoke the predicate or function specified for that method in the instance declaration that matches the types of the arguments to the call.

Note that even if a type class has no methods, an explicit instance declaration is required for a type to be considered an instance of that type class.

Here is an example of some code using an instance declaration:

:- type coordinate ---> coordinate( float, % X coordinate float % Y coordinate ). :- instance point(coordinate) where [ pred(coords/3) is coordinate_coords, func(translate/3) is coordinate_translate ]. :- pred coordinate_coords(coordinate, float, float). :- mode coordinate_coords(in, out, out) is det. coordinate_coords(coordinate(X, Y), X, Y). :- func coordinate_translate(coordinate, float, float) = coordinate. coordinate_translate(coordinate(X, Y), Dx, Dy) = coordinate(X + Dx, Y + Dy).

We have now made the `coordinate`

type
an instance of the `point`

type class.
If we introduce a new type `coloured_coordinate`

which represents a point in two dimensional space
with a colour associated with it,
it can also become an instance of the type class:

:- type rgb ---> rgb( int, int, int ). :- type coloured_coordinate ---> coloured_coordinate( float, float, rgb ). :- instance point(coloured_coordinate) where [ pred(coords/3) is coloured_coordinate_coords, func(translate/3) is coloured_coordinate_translate ]. :- pred coloured_coordinate_coords(coloured_coordinate, float, float). :- mode coloured_coordinate_coords(in, out, out) is det. coloured_coordinate_coords(coloured_coordinate(X, Y, _), X, Y). :- func coloured_coordinate_translate(coloured_coordinate, float, float) = coloured_coordinate. coloured_coordinate_translate(coloured_coordinate(X, Y, Colour), Dx, Dy) = coloured_coordinate(X + Dx, Y + Dy, Colour).

If we call ‘`translate/3`’
with the first argument having type ‘`coloured_coordinate`’,
this will invoke ‘`coloured_coordinate_translate`’.
Likewise, if we call ‘`translate/3`’
with the first argument having type ‘`coordinate`’,
this will invoke ‘`coordinate_translate`’.

Further instances of the type class could be made, e.g. a type that represents the point using polar coordinates.

Next: Abstract typeclass declarations, Previous: Typeclass declarations, Up: Type classes [Contents]