Abstract data types
Sometimes we wish to define a data type that does not expose its representation. Such data types are called abstract or opaque. Suppose we wish to define operations on normalised vectors in 2D space. If we directly exposed the representation, then it would be easy to accidentally break normalisation. To avoid mistakes, we need all operations to go through functions that are careful to re-normalise when needed.
In Futhark, this kind of information hiding is done via the module system. First we define a module type that describes an interface consisting of abstract types and operations on those types:
module type normvec = {
type vec
val mk : {x: f64, y: f64} -> vec
val unmk : vec -> {x: f64, y: f64}
val add : vec -> vec -> vec
}
The module type states that there is some abstract type vec
, a
function mk
that can turn records into
vec
s, and a function unmk
that can turn vec
s into records.
The idea is that the mk
function will perform normalisation. The
module type also specifies a function add
for adding normalised
vectors. This is of course a much smaller set of operations than
you’d need for real programming, but it’s enough to show the idea.
We can define a module that implements the normvec
module type
like this:
module normvec : normvec = {
Note that module names and module type names live in different namespaces, and there is no requirement that the name of a module must match the module type it is implementing.
We first define the vec
type, as a record.
type vec = {x: f64, y: f64}
The definition of vec
is visible inside the module itself, but
outside users are only able to use it through the operations
specified by the normvec
module type.
The mk
function normalises the given coordinates.
def mk {x, y} : vec =
let norm = f64.sqrt (x**2+y**2)
in {x = x / norm, y = y / norm}
The unmk
function just returns the coordinates.
def unmk {x, y} = {x, y}
Finally, the add
function adds the components, then re-normalises
the vector.
def add (a: vec) (b: vec) : vec =
2, y = (a.y+b.y)/2}
{x = (a.x+b.x)/
}
Now we can use the module as e.g:
> let a = normvec.mk {x=2,y=1}
> let b = normvec.mk {x=2,y=10}
> let c = normvec.add a b
> normvec.unmk c
{x = 0.54527166306905f64, y = 0.7138971355954391f64}
If we directly tried to access a.x
, we would get a type error.
Note that if you actually load this into futhark repl
to play
around with it, you’ll be able to directly observe the contents of
normvec.vec
values. To ease debugging, the interpreter does not
respect abstraction boundaries when prettyprinting.
See also
Complex numbers, Triangular arrays, Nominal types, AD with dual numbers, Three-dimensional vectors.
Reference manual: Module System.