A Tour of Morfa

ᴹ, | and []

A more interesting use case of Morfa's operator magic is providing tools to create and concatenate matrices. Consider we want to define the following matrices:

  • A:
    | 1.0 2.0 |
  • B
    | A  |
    | 2A |
  • C
    | B I |
    | 0 B |

We can make Morfa code for this look as neat as:

var A = ᴹ[1.0, 2.0];
var B = ᴹ[A     | 
          2 * A ];
var C = ᴹ[B,        eye(2) |
          zeros(2), B      ];

Notice the leading to explicitly mark that we do want a matrix (opt-in philosophy as outlined before) and the | to denote end of a row.

Let's take this apart. First the leading "operator" , which is not really an operator:

private struct ConcatenationProxy {}
public const mbind = ConcatenationProxy();
public alias ᴹ = mbind;

This may look esoteric at first glance, but the sole purpose of this is to be able to define $[] on something unique denoting matrix concatenation, like this:

template <TList...> func $[] (ConcatenationProxy, TList): Matrix

We are touching on several subjects here:

  • a private struct ConcatenationProxy which only serves the purpose of denoting the circumstances of how is used
  • an alias to a Unicode character for brevity of the concatenation calls
  • and a template-variadic operator $[] which will enclose a sequence of "concatenatees" of various types

The reason we use a template variadic approach is the following:

private struct EndRowType;

public operator |
    kind = infix,
    precedence = assign,
    associativity = left
public func |(left: Matrix, right: Matrix): EndRowType

Inside the [] of the $[] operator above we may have either arguments of type (or convertible to) a Matrix or a new entity: EndRowType that is used to indicate where the rows are split apart. EndRowType doesn't need to do anything sophisticated - it should only keep track of what and how the matrices were written out in the concatenation sequence.

It is all up to implementing the $[] and | to be correctly translated into concatenation "instructions" as used for the matrix var C above. This brings us back to the $[] definition from above:

public template <TList...>
func $[] (c: ConcatenationProxy, argList: TList): Matrix
    // process the argList list of various types one by one
    //  - in case there is a Matrix instance - append to current row
    //  - in case there is an EndRowType instance - append the Matrices it holds and split rows appropriately

Last bit is to have the possibility to stack rows with one entry each, like var D = ᴹ[A | A | A]. The following overload of | should handle this:

public func |(left: EndRowType, right: Matrix): EndRowType

And that is all there is to it! Using very little amount of code we are able to introduce into the language some new constructs which have not been there before. With these user-defined constructs we are enabling coding matrix operations in Morfa.