Perl6::Math::Matrix (Part 3: when to use MMD)
In this guide about what to consider when writing a Perl 6 module, we (after part I and part II) will reach Perl 6's great power of signatures. They enable a feature, that is very often used in Perl 6 (internally and externally). Here are my arguments when the usage of MMD makes the most sense.
In short: Multi Method Dispatch allows you to have multiple methods or subs with the same name but different signatures. When you call the method - the compiler decides according to the type and number of the arguments, which actual method is allowed to run. (And if no signature fits the call, an error will be thrown - same as with any malformed call). But this is just the technical side. There is also the software engineering / API side. When is it useful to combine different functionalities under the same name and when it just muddies the understanding of what it going on? To clear that question I want to look at three (+1) examples from the module at hand.
Unsurprisingly we have a method called new. During the original design, which was the work of Pierre Vigier new just accepted an Array of Arrays of Numbers as in: [[1,2],[3,4]]. It also informed the user how to understand a matrix. Very recently I added the possibility to provide a list of lists of numbers or a String that is a direct tabular representation of a matrix. Of course it makes sense to call all of these methods new. The input of each contains the same data, just in different formats, and the result is also in any case the same - no confusion there.
But we have additional constructors for special matrices, as any system like mathematica or octave has (also for reasons mentioned last time). I named them new-zero, new-identity and so on. Not go for MMD in this case was easy because there are three reasons to do so:
They do functionally something different. The former constructor methods take the data as is and only transformed them into a new format, a Math::Matrix object. These special constructors take only information about the size or the content of some cells and fill up the rest.
There is a clear relation between the name of a special matrix and the signature needed to construct such matrix. The name sets the stage, the signature is subordinated. If we overload new both of them get crammed into the signature as for instance in: Matrix.new('zero', 2, 3), which really doesn't sit well with my intuition too. Its much clearer to pull the decisive name into the method name to make it more clear what will follow: Matrix.new-zero(2, 3).
Last but not least it would be impossible or way too complicated. We would have 7 multi methods called new (more planned) with overlapping signatures. To even be able to separate them, we have to insert the name of the special matrix and then we get into the problem of bullet two.
A norm is a singular number that reflects the abstract concept of size. You can invent any number of different norms, but the (strongly simplified) rule is: the greater the numbers in the matrix - the greater the value of its norm. There are many norms that have a dedicated name. The most famous is the Euclidean, but also the p-q-norm is very wide spread. In fact if you set p = 2 and q = 2, you get the Euclidean. But as you can easily see in this situation, we should fully employ MMD here. First, its conceptually always the same thing you do: you calculate one result value based on all the matrix cell values. And no matter if you write $matrix.norm('column-sum') or $matrix.norm(p:<4>,q:<3>) or $matrix.norm(1) (p-norm with p = q = 1 a.k.a. L1 norm a.k.a. sum of all absolute cell values) - it never gets complicated (speaking about the complexity of the signatures).
More tricky is our third case, where we have three multi methods listening to the name submatrix. Associated with that term is a clear mathematical definition, so surely we have to provide that. So when you call $matrix.submatrix(1,2) the second row and third column will be removed and you get a matrix with the content that is left. To combine that with something that has less academic credibility is the first question mark you may put on my design here. But it felt right to me and I can back it up with reason.
The second method takes a more visual approach. If you write down a matrix as a table on a sheet of paper and draw in it an rectangle you get a submatrix of that second kind - which is also recognized by some sources.
The third variant is a direct extension of the second and a perly Swiss army knife method to get any selection, combination and ordering of rows and columns you imagine.
You can separate all three signatures easily, since the first takes two Int, the second two Ranges of Int and the third two lists of Int. So at least the criteria of simplicity is still upheld. And in any case you get a subset of the original matrix, so the functionality is similar enough to fit a common umbrella term (second principle). The third multi method might be better called rehash or something of that nature. But the force of expectation works in more than one way. Surely the trained mind wants to see under a familiar name just what it already knows and nothing else. But when searching for the latter functionality, it might look at first near something that it knows to be alike. Also a fully unfamiliar name of a method rehash might trigger unwanted confusion. We are here in Perl country, where the word hash calls for strong associations into an entire different direction.
For instance map is another very important keyword in Perl that provokes a clear set of expectations. Math::Matrix provides a map that takes one argument (anonymous block) and walks with is over all cells, calling the block with the cell value as argument. From all the results a new matrix will be built. The module has also an iterator that additionally feeds the block with the indexes of the current cell or just the indexes. Here MMD would be fully misleading, because the functionalities are too different and very unexpected. The name of the second and third method need flashing warning lights, so I called them map-with-index and map-index.