Perl6::Math::Matrix (Part 2: Converter)
When working to build software, there are a lot of data types to consider. In this post, a developer shows how his open source Perl module can help you deal with data.
Join the DZone community and get the full member experience.Join For Free
In this series of articles, I reflect and expand on a talk I gave this year in Glasgow were I spoke about writing Perl 6 modules in general and my module in particular. Part I was about data types I used or wanted to use, because my approach is it to think first about data structures and built later the code around that. It is also crucial because this module basically provides the user with a new data type.
To continue where we left off, this part will be about converting our data type (Math::Matrix) into other types and we start best with the usual suspects. As I said in the intro of my talk: "your main job as a Perl 6 module author is not to screw up the inherent beauty of the internal design." - meaning: you have to provide for a lot of interfaces - read: implement certain methods! Converter to popular data types are just one part of it.
It also makes your data type also much more usable if it provides meaningful information in often used contexts. A context is in Perl 6 also nothing else than a situation where a certain method is called upon you object. And most of the time this method will be a converter into a type as well.
You might not use the prefix operator
? or its word like counterpart "
so" as often, but an "
if" (which also enforces the boolean context as its counterpart the ternary op or any other logical op) is the bedrock of programming. And since this module is obviously about math and all Numeric types (if asked about their boolean value) just check if the value is not zero, we should do the same. It is handy that there is a thing called a zero matrix (when all cells have a value of 0). So in a bool context, I just deferred to the method
.is-zero. This is another P6-convention we should not break: methods that return a Bool start with an "
This context is more tricky since it is enforced by a plus operator and others. I choose it to return the number of cells but I'm not entirely happy with it since the size would be much more useful (a 22 and a 41 matrix are very different things so +$matrix1 == +?matrix2 would be rather misleading,r eturning the two dimensional size would make much more sense, but the method Numeric expects one value. The second issue: it is a mismatch with what I wrote in part one ("I see the matrix as one singular value"), .. and not a container I might add. Plus there there is already a mathematical term for a singular numerical value representing the "size" of a matrix content: norm. That is why it redirects now to the norm method, which gives you the Euclidean norm (sometimes called "Frobenius" or "L2"), when called without arguments. An alternative candidate would be the determinant of the matrix, but not every matrix has one (only square matrices), so its better to take the norm here.
This one is again easy since everyone knows what to expect (I got it wrong the first time anyway — but the audience luckily caught me out). The rows are separated by new lines and the cells by spaces. This allows for nicely formatted matrices within the source code by using heredocs.
Math::Matrix.new: q:to/END/; # indent as you wish 1 2 3 4 END
.new also accepts this format as input, so we are also consistent there.
The main format
.new expects is an Array of Arrays so I should have also a method that can return this format. Plus I implemented the AT-POS method, which allows this syntax $matrix. The method itself returns of course just the array with the values of a row. This is just another builtin mechanism of the language we can easily use by writing a one line method.
This is more of a toy, but when we have the content as an Array, why not have a Hash and work with these subscripts? Since much Perl is about data munging some might even need it to work efficiently on their data because it is important to me to not make the format opaque. You might just use one or two math operations to transform your data and then reuse it elsewhere.
Returning a flat list (which is the expected) is no biggie, but there are several issues with it. You might want a row wise or column wise ordering, or even a list of lists. To fulfill all these needs I also implemented list-rows and list-column, which fulfills the last wish.
list is just an alias to
list-rows.flat, so if it is not what you want you can at least have
Because lists are so useful, this method opens up the module to several worlds of functionalities. One of these is set operators. Does our matrix contain the value 1 ($matrix (cont) 1) or does it contain a value between one or three ($matrix (cont) 1..3) or are all values in the matrix within a set ((2..7) (cont) $matrix)? These are useful things to know and if you don't like this syntax, we also provide a
.cont method ($matrix.cont(1 .. 3) ).
Because lists are so useful, this method opens up the module to several worlds of functionalities. One of these are set operators. Does our matrix contain the value 1 ($matrix (cont) 1). To ease this syntax, there is also a cont - method: $matrix.cont(1). To ask if there are values between one and three in the matrix you could ask: $matrix.cont(1..3). If you want to know if all values of the matrix are in a certain range (are elements of a range), write: $matrix.elem(2..7).
And surely you can map, grep, reduce, etc. on the lists you get out of the matrix content. As a shortcut, the module provides its own map, which maps over all cells and creates a new matrix out of the results (following the logic that a normal map takes a list and transforms it to another list of results). You could also reduce the rows (reduce-rows) or (reduce-columns) to create a list out of the whole matrix. For instance $matrix.reduce-columns(&[+]) gives you a list in which every value is the sum of the values in a matrix column.
.Str is also called by print and put. And frankly it is embarrassing when your user does print $your-datatype and it returns nothing or something that is not representative of the content of your object. Even more crucial and more used is
say, which will call the method
gist on your Object. The name gist actually has a meaning and, for instance, a list will be shortened (to not spam the shell) when you use, say $list. Accordingly, say $matrix will show you a nicely formatted excerpt that fits on a standard terminal. But optional parameters allow you to adjust to any screen size. I'm also glad to announce the main feature of the newly released version 0.3.0 that gist now works with all Numeric types (including Bool and Complex) and it will be formatted in a way so that each column takes minimal space and complex numbers will stick out. I plan to provide a third optional parameter for other formatting rules.
The output of gist will be cached, so after once you set your format, you can later just say $matrix instead of say $matrix.gist(...). (Don't worry you can change the format later by calling gist with different arguments).
And if you want to be very correct: provide 2 .gist multi-methods. One for the defined object and one for the undefined value (type object), because any standard type object is also known by gist (say Int gives you
Even if I provide both data formats accepted by new, I should also have a
. perlmethod, because that is expected from me, the module author even more: to have a method which output can be eval-ed into a clone (for marshaling and other purposes).
Published at DZone with permission of Herbert Breunung, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.