A Tour of Morfa

Value template parameters

In Morfa it is also possible to parametrize declaration with a value. For example, you may declare a generic type Point<N> for points in the N-dimensional Euclidean space:

import morfa.util.Slice;
import morfa.math.base: sqrt;

public template <N: int>
class Point
{
    private var coordinates: float[N];

    public func new(coords: float[N])
    {
        for (i in 0 .. N)
            coordinates[i] = coords[i];        
    }

    public template <K: int> if (K > 0 and K <= N)
    property get(): float
    {
        return coordinates[K-1];
    }
}

unittest
{
    var p = new Point<3>([1.0, 2.0, 3.0]);
    assert (p.get<1> == 1.0);
    assert (p.get<2> == 2.0);
    assert (p.get<3> == 3.0);
    // This would produce a compilation error:
    // var t = p.get<4>;
}

A generic function distance that computes an Euclidean distance between two N-dimensional points is also parametrized by an integer value:

public template <N: int>
func distance(p: Point<N>, q: Point<N>): float
{
    var distance = 0.0;
    for (i in 0 .. N)
    {
        var delta = (p.coordinates[i] - q.coordinates[i]);
        distance += delta * delta;
    }
    return sqrt(distance);
}

Template argument deduction works for value parameters as well: you do not have to provide explicit template arguments to distance below, since they can be deduced from function arguments:

unittest
{
    alias Point3 = Point<3>;
    var p = new Point3([0.0, 1.0, 0.0]);
    var q = new Point3([1.0, 0.0, 1.0]);
    var d = distance(p, q);
    assert (d == sqrt(3.0));
}

A template declaration may contain both type and value parameters. For example, we may declare a type of lists of at most N elements of type T as follows:

public template <T, N: int> if (N > 0)
class BoundedList
{
    public var elem: T;
    public var next: BoundedList<T, N-1>;

    public func new(head: T, tail: BoundedList<T, N-1>)
    {
        elem = head;
        next = tail;
    }
}

A base case for N = 0 may be defined using template parameter specification and the keyword is (an alternative would be to use a constraint with N == 0):

public template <T, N: int is 0>
abstract class BoundedList {}

unittest
{
    var ints0: BoundedList<int,0> = null;
    var ints1 = new BoundedList<int,1>(1, null);
    var ints2 = new BoundedList<int,2>(1, new BoundedList<int,1>(2, null));
}

As was the case with generic lists we defined in section on class and struct templates, it is convenient to introduce a template function for constructing bounded lists:

public template <T, N: int> if (N >= 0)
func cons(head: T, tail: BoundedList<T, N>): BoundedList<T,N + 1>
{
    return new BoundedList<T, N+1>(head, tail);
}

You may also define a const template for creating empty lists:

public template <T, N: int>
const nil: BoundedList<T,N> = null;

Note that null is a valid value of a type BoundedList<T, N> for any N and T, not only for N = 0.

unittest
{
    var list0: BoundedList<int,0> = null;
    var list1: BoundedList<int,1> = cons(1, list0);
    var list2: BoundedList<int,2> = cons(2, list1);
    var list3: BoundedList<int,3> = cons(3, list2);

    var list31: BoundedList<int,3> = nil<int,3>;
    var list32: BoundedList<int,3> = cons(1, nil<int,2>);
}