Sunday, August 30, 2009

Num and +: Roles not always the best approach?

An interesting subject came up on #perl6 this week: .Num and prefix + do not do the same thing. The difference is that .Num always returns a Num or an error. + is much more fuzzily defined (so far as I know): + returns something that you can do arithmetical operations on.

This may sound like splitting hairs, but when you realize that Num is supposed to represent real numbers, you can see it captures an important distinction. For instance, if $z is a Complex, then $z.Num is an error (not even implemented!). On the other hand +$z is a perfectly good Complex number.

This ties into something I mentioned back in the middle of the Vector posts. As an old school C++ programmer, I'm used to thinking of class inheritance as the natural way to write routines which can work on data types you know nothing about, and templates as a tricky alternative which, if you can make it work, significantly reduces the couplings between your classes and your users'. As I understand it, in Perl 6 code the former would be commonly implemented as a role. But crazily enough, the equivalent of the template option is what you get when you just program normally in Perl 6!

Consider a generalized dot product function which takes two arrays of equal length, pairwise multiplies their contents and sums the result. I've been programming C++ for twenty years now, yet the thought of trying to write a properly general version of that in C++ template form stumps me. How the heck can you easily specify that the return value is the type you get when you do the sum? I'm sure there's a way of working it out, but it's decidedly non-trivial. Whereas in Perl 6 it really is trivial:

In fact, in Perl 6 you have to do more to restrict what types the function works on! By default it will work on any combination of things that can be multiplied and added. (In fact, it doesn't have be consistent types: if one array is mixed complex numbers and reals, and the other ints, rationals, and number strings, Perl 6 will automatically sort it all out and do the right thing.) (At least in theory -- your mileage may vary in current builds of Rakudo.)

It seems to me that as Perl 6 programmers, our default position should be to be as general as reasonably possible. Certainly if you are writing math functions, unless you know a variable has to be a real for the math to work, you should not declare it to be a Num, nor should you use the .Num method to make sure it is something you can do math on. For the former, Any or no declared type is better; for the latter, prefer prefix +. Specifying parameter types is mostly useful for controlling overloading operators and functions.

By the same token, those of us implementing numeric classes need to provide as standardized an interface as possible. This includes not only the obvious operators, but prefix + as well -- overloading it is basically announcing to the world "I can do math!" Likewise, you should probably overload the Num method if your type can sensibly be converted to a real. (Mind you, I have no idea how to do that at the moment -- Num is definitely under-spec'ed.)

Now that I've written this, I guess I should go look at implementing these for Vector, eh?

UPDATE: Apparently prefix + cannot be overloaded at the moment, and just calls Num internally. Drat.

WARNING: As far as I know, Num and + are not spec'ed anywhere yet. So this is mostly my interpretation of what I have seen in practice and heard discussed on #perl6. It is both subject to me being completely wrong and being changed in the spec.

1 comment:

  1. Yes -- I tried to convert prefix:<+> into an overloadable operator but ran into some very bizarre errors. I decided to go ahead and commit what was done and look at it in a bit more detail later.

    Oddly(?), there doesn't seem to be any issue with overloading prefix:<-> .