[Perl 6 page]

Checked and updated June 2009.

APL

Perl 6, in a lot of ways, improves and unifies the syntax. But it also makes some things more verbose then they were before, or at least more verbose then they would need to be based on other features. Furthermore, young programmers these days don’t know what line noise is. So, I’ve taken some inspiration from a language that won its creator the Turing Award in 1979, APL. Perl 6 already has hyperoperators to provide SIMD processing without writing explicit loops, and APL also has the feature of being able to (in theory) write any computable program as one line of code, so borrowing more from this vector processing language seems natural.

If you don’t have a font with APL characters, the following will be even more unfathomable.

min and max

Perl 6 has min and max chaining operators. For example,

my $min = $a min $b min $c min $d;
my $max = $a max $b max $c max $d;

$max = [max] @array;

The reduction metaoperator form is not bad, as you only have to say max once. So perhaps I would just write the first one as [max] $a, $b, $c, $d. After all, the operator name is longer than any of the operands! That just will not do.

As a first example, it’s quite easy to define to work exactly like max.

sub infix:<⌈> ($left, $right)
 is inline
 is equiv(&infix:<max>)
 {
  return $left max $right;
 }

The syntax for operator names should be clear. The is equiv trait clones all the operator-related traits of the max operator, including its precedence and associativity. In particular, it will be chaining, just like max.

The implementation is trivial. It just calls the other function with the same arguments. No types are given on the parameters or return, and we hope that the inlining of the function will allow the optimizer to propagate the argument types (if known) directly to max.

my $max = $a ⌈ $b ⌈ $c ⌈ $d;

Now that’s more like it!.

rho

In Perl 6, the number of elements in an array can be determined by using @A.elems, which is more verbose than just letting @A in numeric context become the length. But, the old behavior is still there as well, so no problem. In fact, if you need to force the issue or otherwise be explicit, you can now use a prefix + instead of the keyword scalar.

But, arrays can be multi-dimensional in Perl 6. Here is how to make the operator work as expected:

sub prefix:<⍴> (@A)
 is inline
 {
  return @A.shape;
 }

# example usage
my @A [2;3;4];
say ⍴@A;  # shows "2 3 4"

Of course, the same symbol may be used as an infix operator and a prefix operator at the same time, without confusion. After all, even + does that. So how do we reshape an array? As long as all the dimensions are fixed, this should do the trick:

sub infix:<⍴> (@@shape, @A)
 is inline
 {
  my @result = .new(shape=>@@shape);
  my $Rit = @A.Iterator;
  my $Lit = @result.Iterator;  # relies on iterator being "by ref"
  @$Lit = @$Rit;
  return @result;
 }

# example usage
my @X = (2;3;4) ⍴ ^24;
  #  note that ^24 is Perl for ⍳24
sub infix:<⍴> (@@shape, @A)
 is inline
 {
  my @result = .new(shape=>@@shape);
  @result = @A;
  return @result;
 }

I think the variation on the right should work as well. The list assignment operator is not well specified in the Synopses, but since it fills containers on the left, and is flattening list context on the right, it makes sense that the list of elements on the right should pour into the shape of the container.

Reading Input

In Perl 6, reading input from either standard input or each named file in turn is done by reading from the $*ARGS handle. So where in earlier versions of Perl you just wrote $x = <>, now you have to write $x = $*ARGFILES.get. And in a Perl command-line program, what’s more common than reading the next line of input?

Arranging for (quote-quad) to return the next line of input is a little different. That’s not a letter, so you can’t write it as a function. But who ever heard of an operator that takes no arguments? In Perl 6, the grammatical category for that is term.

sub term:<⍞> ()
is inline
 {
  return $*ARGFILES.get;
 }

Who needs loops?

APL contains the ¨ operator, meaning “each”. The term operator in APL means something that takes a function as an argument. For example, 1 2 3 ⍴¨ 10 would natively be written in Perl 6 as 10 «xx« (1,2,3).

Perl 6 has reduction operators as we saw earlier, which sticks the function between each argument in the list. So where APL would have +/, Perl 6 would use [+] for the same meaning.

The Synopses don’t say how that works on multidimensional arrays. So let’s implement the APL operator which works on the columns of a matrix. How do we write a function that works with other functions as parameters?

sub infix:<⌿> (@m, &f)
is inline
 {
  my ($rows,$cols) = ⍴@m;
  my @result[$cols];
  @result[*] = @m[0;*]; # copy first column
  for 1..$rows-1 --> $r {
     for ^$cols --> $c {
	    @result[$c] = f(@result[$c], @m[$r;$c]
        }
     }
  return @result;
 }

Quite simply, the sub takes two arguments: the second is a function and the first is the matrix, and it returns a vector. So how do you call it? Like you do with sort, grep, and map, you can put a block there, or you can refer to a named function. But what is the built-in + function? It does indeed have a name, and referring to &infix:<+> with no further qualification will refer to the whole set of MMD variations, containing the dispatch logic to call the right one at run time. Later, the shortcut &[+] was added for infix operators.

@r = @matrix ⌿ &[+];

But that is still a far cry from the ease of the built-in reduction operator syntax. What I need is a macro to enable the desired syntax. The meta-syntax needs to surround the operator, because the name can have multiple characters. And, since we already have [op] and [\op] which is the same as APL’s \ (scan), let’s make this [-op], since the - is what turns the APL reduce into the column-reduce.

macro circumfix:<[-   ]> ($op) 
is parsed / .*? /
 {
  blah blah blah... emit code that includes reference to:
     ... infix:{$op} ...
 }

Since the matrix is not passed in to the macro, the macro must substitute something that can then be followed immediatly by the matrix argument, without any need to add parens around it or do anything else after it. An infix operator similar to the original ⌿, but with the arguments in the opposite order, would do the trick.

Perhaps Perl 6 should add a grammatical category for defining reduction-like operators, since there are already a couple supplied. But that’s still too specialized to do everything we might want. APL has a generalized inner product operator which takes two functions and the data! An obvious way to code that would be the macro:

macro circumfix:<[   ]> ($op1, $op2) 
is parsed / (.*?)<ws>(.*?) /
 { ... }

But that would conflict with the existing curcumfix notation. Unless the first one was written to accomidate multiple functions inside the brackets separated by whitespace, and treat the normal meaning as a degenerate case, we would have to use a different notation. But in principle, you see that writing a function that takes two other functions and some data is no issue, and customizing the notation and giving a little syntactic sugar is possible with macros.

Anything that can’t be done with macros (or is hackish to do) can be done with full-blown syntactic warping, or slangs. That is, you can change the grammar of Perl 6 on the fly! And you can extend the classes used by the compiler, supply code for callback hooks, etc.

but seriously

In principle, everything that is part of Perl 6 can be written in Perl, meaning you can write things that are similar to built-in things and both look and work the same. This is important because desired features can be made into libraries found on CPAN6 and contributed by independant developers, rather than magically built in to the core language. And much of the standard distribution can be written with portable Perl 6 code as library not not specially developed in the low-level core.

As an intellectual exercise, these examples also bring to light some design issues for Perl 6 and its standard library.

The Synopses discuss iterators, but does not show specifically how to create an iterator for an Array, and we’ve identified a need that such iterators ought to be (at least optionally) rw bound to the container’s elements in the same manner as a for loop.

clarified in Synopses August 2008. There ought to be a way to treat a whole MMD set of overloaded functions as a function object.

There should be hooks in the standard grammar so it is easy to write reduce variants as shown here, and use a common meta-syntax.

We need to make sure that context (list or item) propagates into functions in the same way as with earlier versions of Perl.

It is confusing that the shape function takes a slice and not a regular list. What would a regular list mean? If it flattens, then either would work.

List assignment to shaped containers should work as illustrated.

Doing Better Than APL

It’s been suggested that Perl 6 can do better than APL in that APL has some operators that work on rows and some that work on columns, but Perl 6 could take an argument that specifies the direction and dimension in a fully general way.

A very cool feature of Perl 6 is that infix operators can take extra arguments, besides the normal two. Now due to the syntax of an infix operator, just where do you put the extra ones? This uses the adjective notation.

$y = $x + $b :carefully;

The :carefully adds the named argument, which is shorthand for :carefully<True>, to the + operator in addition to the left and right arguments. So, you could write an operator that took optional parameters to describe the direction or whatever.

Perl 6 also has slices. Given a two-dimensional array @A, you would write @A[2,3] to get that single element, for example, or @A[2] to get the entire row. So you are used to in other languages. But in Perl you can also write @A[*;3] to get the entire column. So if you wanted to total up this column, for example, you could write [+] @A[*;3].

To total all columns, you would need [+] @A[*;$_] for ^@A. Using @A as a number will be the number of elements in the first dimension. The ^ is explained in another essay.