|There's more than one way to do things|
From other recient posts, I found Higher Order Perl free for the downloading. I think I had seen it before a few years ago. But now I have Perl 6 functions and "subtypes" on the brain, so this occurs to me as I read it.
The original function,
First, port that to Perl 6 without changing much.
The step 4 is edited because catenation is now done with ~ rather than a dot. The sub has a declared parameter rather than a line to extract it, and lookie here, the if statement....? $n == 0|1. This is a junction, and means just what it says: $n is equal to (0 or 1)? Picture a longer group of comparisons, as I'm sure you've had in the past, and the savings and readability mounts.
Now comes the feature I was reminded of. Perl 6 supports overloading. And you can overload like this:
That is, a literal is considered a subset type of one value. And Junctions can be used with types as well, so you can write recursive functions with the base case as a separate function, rather than dividing it up explicitly inside the function.
Furthermore, the function has a flaw in returning funny stuff if the argument is not an integer, and may do one of several wrong things if the argument is negative. Both of these can be fixed by using overloading as well:
Change the main form from untyped to taking an Int. Now a floating point value will be truncated before the first call, avoiding the two problems the existing code has along these lines. Second, use the same overloading mechanism to look for negative values. Perhaps there will be standard types for negative, non-positive, etc. as there are in XSD. But it's not worth the trouble to declare one for a single use:
Assuming you didn't mean two's complement or something else. Hmm, that should be specifiable. Since Int does not have a word size inherent with it (it holds any size Int subject to memory limits), you have to tell it the word size for 2s complement, and that can serve as an enabling switch:
The :$wordsize with the leading colon declares a named (only) parameter, so you can now say say binary($n,:wordsize<64>) and if $n happens to be negative, you use the extra argument. In the other forms, it's harmlessly ignored.
The other interesting thing is the line that computes the 2's complement. I worried about the wordsize not being long enough, and in that case the subtraction as formulated would still be negative. Meanwhile, I could not store the result back into $n ($n= blah -$n) because the parameter is read-only. Declaring it otherwise would not help, since it is also declared as Negative. The result is supposed to be positive, so would be a type error. So... rather than use a normal Int for $m, I made that one a PositiveInt. That documents the intent neatly, and automatically gives me the test for the result not being negative also (and I know it's not zero).
Perhaps that's not the best error to get when it happens. But, think about this: if I had used restrictive typing as a matter of course and forgotten to check for this directly, I'd get the developer-friendly error rather than an infinite recursion when the inevitable happened. Then I might put in a manual error check to give a better message and carp it, but I'm letting declarations do the error checking I may have forgotten. In more complex situations, that could be less obvious.
Lesson: declared typing is declaring your assumptions.
But, there is still another issue. The wordsize might be some goofy value like "hello" or ¾ or -3+π\i. The easiest thing is to declare that to be PositiveInt as well. (In case you are wondering, π\i is because πi would run together into one word. The \ between letters is a degenerate unspace and serves to break the tokens without introducing whitespace. For 7i and such you don't have the problem)
Putting the condition in the type declaration itself is powerful because you do more than just check for the correct range. You check the overall type as well. If you wrote
you are not only being unwieldy, but the test might not even make sense if the variable is some totally different type and not a real number at all. Type checking offers a powerful constraint mechanism just by declaring it.
For cases that require interrelationships between values, you do have to write the check inside the function. And the PRE block, as shown, formalizes this test as a precondition. (Conjecture: failing a precondition may optionally indicate "no fit" just like parameter types, and cause the dispatch mechanism to look for another match. Do that by throwing an exception of a built-in type for the purpose rather than just evaluating as False)
And that's only the first, rather trivial code example in the book! I think it will take me a long time to get through it again.
Comments welcome: I'll probably put these on a web page after I finish.
Updated: fix paste error in "original" listing; correct subtype to subset type.