Hare by Example: Interfaces

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