Back in 2001 I was working for a company who had a client who was in a serious bind: the maker of their point of sale (POS) system suddenly jacked up the license fee to the point where our client would go out of business. They needed a new POS in 21 days.

We grabbed an open source POS system and identified all of the features it was missing that our client would need. Then it was 21 days of overtime and no days off. Back in the days of use.perl.org, I blogged about this hell almost every day. It was also, interestingly, the first project I wrote software tests for. The other main dev on the project was teaching me how Perl's testing tools worked and as the days went on, I found myself incredibly proud of seeing all of those tests pass and catching bugs I would not have otherwise caught.

Then disaster struck: we tried to actually run the software instead of just testing it. The Tk panels would appear, and then instantly crash again. Adding some debugging code showed that we had a bit of a mess because we unit tested the code. The different parts of the software were isolated in our testing. The individual bits worked fine, but they had no idea of how to talk to one another.

Passing bad data around is an incredibly common source of bugs. In Perl 6, this is not only easy to avoid, but the tools to do so are also far more powerful than most mainstream languages.

What follows is drawn from my Perl 6 for Mere Mortals talk.

Recursion is often taught with the Fibonacci series. Some people hate that because they don't use the Fibonacci series, but from a teaching standpoint, it's useful because it's dead-simple to understand. Here's the mathematical description:

F 0 = 0 F 1 = 1 F n = F n-1 + F n-2

The recursive version looks like the following Perl 6 code:

sub fib($nth) { given $nth { when 0 { 0 } when 1 { 1 } default { fib($nth-1) + fib($nth-2) } } }

Note that the body of the given block effectively mirrors the mathematical description. Thus, recursion can be a great way to cleanly write a function. However, recursion has three very common failure modes:

Failing to validate your arguments (not restricted to recursion, of course) Failure to provide the base case(s) to break recursion (we have them above) Excessive recursion depth blowing the stack (we have that in spades)

In the above, you can say fib(3.7) and it we have an infinite loop because n-1 and n-2 will never match our base cases (though you'll blow the stack quickly). How would we handle that in Perl 5? Well, there's more than one way to do it:

sub fib { die unless $_[0] =~ /^\d+$/; ... sub fib { die unless $_[0] == int($_[0]); ... use Regexp::Common; sub fib { die unless $_[0] =~ $RE{num}{int}; ...

Each of the above has multiple bugs lurking in them. You can have fun seeing if you can identify all of them. What's worse, this is what we often find:

sub foo { my ( $self, $this, $aref, $hashref ) = @_; # lots and lots of skipped validation ... }

Validating our data in most dynamic languages is done in a very ad-hoc manner. In fact, it's often not done at all and we just hope that things work. In practice, they do. At 2AM when you're frantically trying to chase down where the bad data came from, you might find yourself cursing and wishing you had a stricter type system so that the bad data can be caught as soon as it occurs.

Gradual Typing

Perl 6 has that stricter type system. It's completely optional and called gradual typing. Basically, it works like this:

sub fib(Int $nth) { given $nth { when 0 { 0 } when 1 { 1 } default { fib($nth-1) + fib($nth-2) } } }

You see that Int $nth in the signature? It's nice and simple. It's there if you want it, but you don't need to use it. It's also more likely to be used because it's so simple.

(As an aside: in Perl 5, a scalar internally has multiple "slots" to store data in different representations. By declaring the type of a variable, we no longer need to have as many slots and there are plenty of performance optimizations available.)

Subsets

But what if we ask for fib(-3) ? There is no -3rd fibonacci number, so let's toss on a constraint:

sub fib(Int $nth where * >= 0) { given $nth { when 0 { 0 } when 1 { 1 } default { fib($nth-1) + fib($nth-2) } } }

It's the where * >= 0 bit we care about here.

The asterisk is an example of the Whatever type. When I see it, I like to read it as "whatever I got". So the argument is "an integer called $nth where whatever I got is greater than or equal to zero".

That's actually pretty easy to read and understand and, more interestingly, most mainstream computer languages simply don't support something like that. This will make it very easy to ensure we don't get passed bad data.

But it gets even better! The above gets clumsy if we have several arguments and it's not reusable. So let's pull that out into a subset that we can reuse wherever we need to declare a type.

subset NonNegativeInt of Int where * >= 0; sub fib(NonNegativeInt $nth) { given $nth { when 0 { 0 } when 1 { 1 } default { fib($nth-1) + fib($nth-2) } } }

That's right. Using subsets, you can easily declare your own types on the fly. Other languages tend to allow this but require you to create a class to define your type. When using Perl 6 subsets, you'll find that having a full-blown class for every type you need is rather clumsy (that being said, everything is an object in Perl 6, but the sugary frosting is awesome).

As an aside, I predict that one of the most common subsets is going to be this one:

subset NonEmptyString of Str where *.chars > 0;

No more checking to see if the string is empty! Now you can just assert it and Perl 6 will check for you.

Or here's something that will come in very handy with an advanced ORM. What do you do if you have an field declared as a VARCHAR(255) ? Assuming that you don't want to allow empty strings and the data must be less than 256 characters:

subset FirstName of Str where 0 < *.chars < 256;

We can now create our own types on the fly to mirror what we have in our database. Somebody forgot to configure MySQL in strict or traditional mode? Who cares! No more silent truncation of our data in the database! I'm looking forward to ORMs which take advantage of this.

Built-in Memoization

Getting back to our Fibonacci function, that code will blow our stack with larger numbers, so what do we do?

All recursive functions can be rewritten as iterative, but the larger the function, the harder that is to do. Plus, we lose the elegant simplicity of mirroring the mathematical definition and have to walk carefully through the code to see if the behavior is the same.

We could rewrite the function to handle the caching, but that gets ugly, is prone to bugs (as I had in my slides), and also obscures the intent of the code:

sub fib(NonNegativeInt $nth) { state %fib_for; unless %fib_for{$nth}:exists { given $nth { when 0 { return 0 } when 1 { return 1 } default { %fib_for{$nth} = fib($nth-1) + fib($nth-2) } } } return %fib_for{$nth}; }

There are plenty of ways to deal with this, but Perl 6 provides caching natively:

sub fib(NonNegativeInt $nth) is cached { given $nth { when 0 { 0 } when 1 { 1 } default { fib($nth-1) + fib($nth-2) } } }

Nice! We now have a fairly powerful type constraint, easy-to-read code, and caching. No rocket science here. It just works.

Return types

Getting back to the huge class of bugs I was first talking about: different functions talk to each other and it's important that they send and receive the correct data. So let's look at this:

sub will-it-blend (NotAnAnimal $something) returns Bool { if $something.does('Blendable') { return True; } else { return blend-it($something); } }

What does blend-it() return? If it doesn't return a boolean, this code will throw a runtime error rather than risk silently corrupting your code. Part of the reason why tests are more common (and more verbose) in dynamic languages is because it's so hard to test the data being passed around. Integration tests are particularly bad at this. Now, it becomes trivial, though still optional.

So our Fibonacci function now looks like this:

sub fib(NonNegativeInt $nth) is cached returns NonNegativeInt { given $nth { when 0 { 0 } when 1 { 1 } default { fib($nth-1) + fib($nth-2) } } }

That's powerful, expressive, and very, very easy to read. It's not nearly as difficult as people are worrying about.

Conclusion

Many people who are working on Perl 6 are pretty excited about what it can do, but when you read blog posts about using red-black trees in Perl 6, your eyes tend to glaze over the same way mine do when someone starts yammering on about football statistics.

Perl 6 is big and yes, there are complex corners of it, but those are often for the hard tasks. In your day-to-day code, it won't look any more difficult than any other language. In fact, just like any new language you learn, you'll start with "baby Perl 6" and gradually move on to more complicated code.

Grab rakudobrew and check out Perl 6 today.