-
Notifications
You must be signed in to change notification settings - Fork 54
Abstract types
An abstract type is a type whose representation is hidden from its users. This is useful to prevent code inadvertently depending on the representation, allowing you to change the representation later without breaking that code. Another reason to use abstract types is if the data representation requires certain invariants to be maintained. By restricting the code which can work directly on the underlying representation, you limit the code that might accidentally break the invariant required by that representation, and the amount of code that needs to be inspected if something goes wrong.
In Mercury, an abstract type is declared in the interface section of a module, then defined in the implementation section of the module. The definition may be of a discriminated union type or an equivalence type.
Example:
:- module mytree.
:- interface.
% Declaration of an abstract type.
%
:- type mytree(T).
:- pred init(mytree(T)::out) is det.
:- pred insert(T::in, mytree(T)::in, mytree(T)::out) is semidet.
:- pred contains(mytree(T)::in, T::in) is semidet.
:- implementation.
% Definition of the type.
%
:- type mytree(T)
---> nil
; node(mytree(T), T, mytree(T)).
% implementation of predicates omitted
Other modules can import the module and see that the type mytree(T)
exists.
The data constructors nil/0
and node/3
are not exported, so other modules
cannot construct or deconstruct mytree(T)
terms directly.
They can only work with values of mytree(T)
using the the predicates and
functions provided by the mytree
module, or one of its sub-modules.
(By the visibility rules for sub-modules, any descendant module of mytree
would have details of the type representation of mytree(T)
.)
Note that code in other modules will still able to compare two terms of
an abstract type for equality, or compare them with the compare/3
predicate.
Also, it is possible to construct and deconstruct terms without access
to the type's data constructors, using the construct
and deconstruct
library modules. As an example, the io.write
family of predicates
write terms by deconstructing them at runtime, which includes terms of
abstract types. The io.read
family of predicates is able to construct
terms of a given type at runtime, including terms of abstract types.