Sign In/My Account | View Cart  
advertisement


Listen Print

Exegesis 6
by Damian Conway | Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11

Editor's note: this document is out of date and remains here for historic interest. See Synopsis 6 for the current design information.

The Wonderful World of Junctions

Perl 6 introduces an entirely new scalar data-type: the junction. A junction is a single scalar value that can act like two or more values at once. So, for example, we can create a value that behaves like any of the values 1, 4, or 9, by writing:

$monolith = any(1,4,9);

The scalar value returned by any and subsequently stored in $monolith is equal to 1. And at the same time it's also equal to 4. And to 9. It's equal to any of them. Hence the name of the any function that we used to set it up.

What good it that? Well, if it's equal to "any of them" then, with a single comparison, we can test if some other value is also equal to "any of them":

if $dave == any(1,4,9) { print "I'm sorry, Dave, you're just a
square." }

That's considerably shorter (and more maintainable) than:

if $dave == 1 || $dave == 4 || $dave == 9 { print "I'm sorry, Dave,
you're just a square." }

It even reads more naturally.

Better still, Perl 6 provides an n-ary operator that builds the same kinds of junctions from its operands:

if $dave == 1|4|9 { print "I'm sorry, Dave, you're just a square."
}

Once you get used to this notation, it too is very easy to follow: if Dave equals 1 or 4 or 9....

(Yes, the Perl 5 bitwise OR is still available in Perl 6; it's just spelled differently now).

The any function is more useful when the values under consideration are stored in a single array. For example, we could check whether a new value is bigger than any we've already seen:

if $newval > any(@oldvals) { print "$newval isn't the smallest."
}

In Perl 5 we'd have to write that:

if (grep { $newval > $_ } @oldvals) { print "$newval isn't the
smallest." }

which isn't as clear and isn't as quick (since the any version will short-circuit as soon as it knows the comparison is true, whereas the grep version will churn through every element of @oldvals no matter what).

An any is even more useful when we have a collection of new values to check against the old ones. We can say:

if any(@newvals) > any(@oldvals) { print "Already seen at least
one smaller value." }

instead of resorting to the horror of nested greps:

if (grep { my $old = $_; grep { $_ > $old } @newvals } @oldvals)
{ print "Already seen at least one smaller value." }

What if we wanted to check whether all of the new values were greater than any of the old ones? For that we use a different kind of junction — one that is equal to all our values at once (rather than just any one of them). We can create such a junction with the all function:

if all(@newvals) > any(@oldvals) {
    print "These are all bigger than something already seen."
}

We could also test if all the new values are greater than all the old ones (not merely greater than at least one of them), with:

if all(@newvals) > all(@oldvals) {
    print "These are all bigger than everything already seen."
}

There's an operator for building all junctions too. No prizes for guessing. It's n-ary &. So, if we needed to check that the maximal dimension of some object is within acceptable limits, we could say:

if $max_dimension < $height & $width & $depth {
    print "A maximal dimension of $max_dimension is okay."
}

That last example is the same as:

if $max_dimension < $height
&& $max_dimension < $width
&& $max_dimension < $depth {
    print "A maximal dimension of $max_dimension is okay."
}

any junctions are known as disjunctions, because they act like they're in a boolean OR: "this OR that OR the other". all junctions are known as conjunctions, because they have an implicit AND between their values — "this AND that AND the other".

There are two other types of junction available in Perl 6: abjunctions and injunctions. An abjunction is created using the one function and represents exactly one of its possible values at any given time:

if one(@roots) == 0 {
    print "Unique root to polynomial.";
}

In other words, it's as though there were an implicit n-ary XOR between each pair of values.

Injunctions represent none of their values and hence are constructed with a built-in named none:

if $passwd eq none(@previous_passwds) {
    print "New password is acceptable.";
}

They're like a multi-part NEITHER...NOR...NOR...

We can build a junction out of any scalar type. For example, strings:

my $known_title = 'Mr' | 'Mrs' | 'Ms' | 'Dr' | 'Rev';
if %person{title} ne $known_title {
    print "Unknown title: %person{title}.";
}

or even Code references:

my &ideal := \&tall & \&dark & \&handsome;
if ideal($date) {   # Same as: if tall($date) && dark($date) && handsome($date)
    swoon();
}

The Best of Both Worlds

So a disjunction (any) allows us to create a scalar value that is either this or that.

In Perl 6, classes (or, more specifically, Class objects) are scalar values. So it follows that we can create a disjunction of classes. For example:

Floor::Wax | Dessert::Topping

gives us a type that can be either Floor::Wax or Dessert::Topping. So a variable declared with that type:

my Floor::Wax|Dessert::Topping $shimmer;

can store either a Floor::Wax object or a Dessert::Topping object. A parameter declared with that type:

sub advertise(Floor::Wax|Dessert::Topping $shimmer) {...}

can be passed an argument that is of either type.

Matcher Smarter, not Harder

So, in order to extend &part to accept a Class as its first argument, whilst allowing it to accept a Code object in that position, we just use a type junction:

sub part (Code|Class $is_sheep, *@data) {
    my (@sheep, @goats);
    for @data {
        when $is_sheep { push @sheep, $_ }
        default        { push @goats, $_ }
    }
    return (\@sheep, \@goats);
}

There are only two differences between this version and the previous one. The first difference is, of course, that we have changed the type of the first parameter. Previously it was Code; now it's Code|Class.

The second change is in the body of the subroutine itself. We replaced the partitioning if statement:

for @data {
    if $is_sheep($_) { push @sheep, $_ }
    else             { push @goats, $_ }
}

With a switch:

for @data {
    when $is_sheep { push @sheep, $_ }
    default        { push @goats, $_ }
}

Now the actual work of categorizing each element as a "sheep" or a "goat" is done by the when statement, because:

when $is_sheep { push @sheep, $_ }

Is equivalent to:

if $_ ~~ $is_sheep { push @sheep, $_; next }

When $is_sheep is a subroutine reference, that implicit smart-match will simply pass $_ (the current data element) to the subroutine and then evaluate the return value as a boolean. On the other hand, when $is_sheep is a class, the smart-match will check to see if the object in $_ belongs to the same class or some derived class.

The single when statement handles either type of selector — Code or Class — auto-magically. That's why it's known as smart-matching.

Having now allowed class names as selectors, we can take the final step and simplify:

($cats, $chattels) = part { .isa(Animal::Cat) } @animals;

to:

($cats, $chattels) = part Animal::Cat, @animals;

Note, however, that the comma is back. Only blocks can appear in argument lists without accompanying commas, and the raw class isn't a block.

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11

Next Pagearrow