Day 20: Little big things

Today we look at some little big things…

There are many simple yet powerful ideas baked-in to Perl 6 that allow many wonderful things. One of these simple ideas is introspection. Introspection is the act of observing yourself. For a programming language this means that there is some mechanism by which you, the programmer, can express questions about the language in the language itself. Perl 6 is a language that supports introspection in several ways. For instance, there are methods on object instances that tell to what class the object belongs, there are methods on classes to tell what methods are available to the class, etc.

There are even methods on subroutines to ask what the subroutine’s name is:

    sub foo (Int $i, @stuff, $blah = 5) { ... }
    say &foo.name;      # outputs "foo"

Now that may seem slightly pointless, but keep in mind that subroutines can be assigned to scalars or aliased to other names or may be generated on-the-fly, so it’s name may not be immediately obvious by glancing at the code.

    my $bar = &foo;
    # ... and then much later ...
    say $bar.name;      # What was this subroutine's name again?

Here are a few other items you can introspect on subroutines.

    say &foo.signature.perl;        # What does the subroutine signature look like?
    say &foo.count;                 # How many arguments does this subroutine take?
    say &foo.arity;                 # How many are required?

That last thing we introspected from the subroutine was its arity; the number of required arguments for a subroutine/method. Because Perl can now figure that information out for itself via introspection, interesting new things are available that weren’t easy or were even non-existent before. For instance, in Perl 5, map blocks take a list of items one at a time and transform each into one or more new items to create a new list, but because Perl 6 knows how many arguments are expected, it can take as many as needed.

    my @foo = map -> $x, $y { ... },  @bar;             # take @bar two at a time to generate @foo
    my @coords = map -> $x, $y, $z { ... }, @numbers;   # take @numbers three at a time

Another benefit of this ability to introspect the number of parameters a subroutine requires is a nicer mechanism for sorting arrays by some other criteria than the default string comparison. The sort method on arrays takes an optional subroutine to act as the comparator and ordinarily this subroutine takes 2 parameters–the items of the array to be compared. So, if you were modeling people and their karma, and wanted to sort an array of people by karma, you’d write something similar to this:

#!/usr/bin/perl6

use v6;

class Person {
    has $.name;
    has $.karma;

    method Str { return "$.name ($.karma)" }  # for pretty stringy output
}

my @names = <Jonathan Larry Scott Patrick Carl Moritz Will Stephen>;

my @people = map { Person.new(name => $_, karma => (rand * 20).Int) }, @names;

.say for @people.sort: { $^a.karma <=> $^b.karma };

But! Since Perl 6 can introspect the comparator, we’ve got another option now. By passing a subroutine that only takes 1 parameter, Perl 6 can know to automatically do the equivalent of a Schwartzian Transform. The above sort can now be written like so:

    .say for @people.sort: { $^a.karma };

But wait! There’s another small syntactic advantage now that there’s only one parameter to the subroutine: implicit aliasing to $_. So we can eliminate the auto-declared placeholder variable entirely:

    .say for @people.sort: { .karma };

What this does is call the .karma method on each element of the array one time (rather than twice for each comparison as would be done with the normal comparator) and then order the array based on the results.

Another little big thing is that Perl 6 has a built-in type system. You may have noticed in the karma example above that I didn’t specify that numeric comparison should be used. That’s because perl is smart enough to figure out that we’re using numbers. If I had wanted to force the issue, I could have used a prefix + or ~:

    .say for @people.sort: { +.karma };     # sort numerically
    .say for @people.sort: { ~.karma };     # sort stringily

One place this is particularly handy is with the .min and .max methods. These methods also take a subroutine to set user-defined criteria for the ordering of elements:

    say @people.min: { +.karma }         # all numbers, so numeric comparison
    say @people.max: { ~.name }         # all strings, so string comparison

If you read yesterday’s advent post, you’ll note that there is another way to write these last few examples using a Whatever:

    .say for @people.sort: *.karma;
    say @values.min: +*.karma;
    say @values.max: ~*.name;

Which is another little big thing in Perl 6. Be sure to check out the other advent entries for even more little big things

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.