Pointers to struct are able to leverage automatic sub-typing. As long as the struct has a pointer to a second struct at the beginning it can be cast to the second type allow you to have abstract interfaces. The `io` module makes heavy use of this pattern, but let's see how we can define our own.
use fmt;
// We define our behavior functions that we will implement in our own
// version later on.
type increaser = fn(o: *operator, ints: int...) int;
type decreaser = fn(o: *operator, ints: int...) int;
// We construct a virtial table to encompase these behavior
type vtable = struct {
increaser: nullable *increaser,
decreaser: nullable *decreaser,
};
// We define a friendly name that we'll use to pass around and reference
// throughout the implementation if we need it.
type operator = *vtable;
// This is our collector func that will accent some implementation of a
// operator and we'll run the operator's increaser and decreaser on the
// ints provided. Notice that this function does not care what the
// operator is, it just calls its functions as long as they are not null
// and returns a tuple of each result.
fn collect(o: *operator, ints: int...) (int, int) = {
const inc = match (o.increaser) {
case null =>
abort();
case let i: *increaser =>
yield i(o, ints...);
};
const dec = match (o.decreaser) {
case null =>
abort();
case let d: *decreaser =>
yield d(o, ints...);
};
return (inc, dec);
};
// Here we define our addsub implementation of the above operator and
// set the internal vtable to the specific operator's vtable below. We
// even have some state unique to this to set an initial value.
type addsub = struct {
vtable: operator,
as_init: int,
};
// This constant struct defines the specific increaser and decreaser
// functions that implement the operator. In this case they are add and
// sub respectively.
const addsub_vtable: vtable = vtable {
increaser = &add,
decreaser = &sub,
};
// The first thing we do is cast the generic operator to our addsub
// type in order to get the state value we need to perform the
// calculations.
fn add(o: *operator, ints: int...) int = {
let operator = o: *addsub;
let sum = operator.as_init;
for (let i .. ints) {
sum += i;
};
return sum;
};
fn sub(o: *operator, ints: int...) int = {
let operator = o: *addsub;
let diff = operator.as_init;
for (let i .. ints) {
diff -= i;
};
return diff;
};
// This creates a new instance of addub and sets the vtable which
// implements the operator functions on it so it has add and sub
// functions. We also set the initial state value for this
// implementation.
fn newaddsub(i: int) addsub = {
return addsub {
vtable = &addsub_vtable,
as_init = i,
};
};
// For completeness we do a whole new implementation of operator, but
// this time we use mul and div for the increaser and decreaser. Notice
// the md_init struct field is different from the addsub version to show
// different struct fields can be in different implemetations.
type muldiv = struct {
vtable: operator,
md_init: int,
};
const muldiv_vtable: vtable = vtable {
increaser = &mul,
decreaser = &div,
};
fn mul(o: *operator, ints: int...) int = {
let operator = o: *muldiv;
let prod = operator.md_init;
for (let i .. ints) {
prod *= i;
};
return prod;
};
fn div(o: *operator, ints: int...) int = {
let operator = o: *muldiv;
let div = operator.md_init;
for (let i .. ints) {
div /= i;
};
return div: int;
};
fn newmuldiv(i: int) muldiv = {
return muldiv {
vtable = &muldiv_vtable,
md_init = i,
};
};
export fn main() void = {
// We create an instance of each implemented operator and
// passing in the inital state value.
const addsuber = newaddsub(5);
const muldiver = newmuldiv(2);
// Set up some data
const ints: []int = [2, 4, 6];
// Run collect which accepts an operator so we pass it our
// addsub implementation to get the results for the ints
const (a, s) = collect(&addsuber, ints...);
fmt::println(a, s)!;
// Run collect which accepts an operator so we pass it our
// muldiv implementation to get the results for the ints
const (m, d) = collect(&muldiver, ints...);
fmt::println(m, d)!;
// Each time we called collect we passed it a different
// operator and internally it ran those operator's increaser and
// decreaser functions correctly.
};
$ hare run interfaces.ha
4/4 tasks completed (100%)
17 -7
96 0
Back to table of contents