2018-11-06

Perl6 appetizer

Perl5 is a nice useful language, I’ve always liked it, though sometimes it felt a little bit clumsy. I had some metaphorical headache managing arrays and hashes in few circumstances, for instance, and I’ve never get really accustomed to the lack of a signature in subroutines. I’ve barely seen the “new” OO features, and when I tried them, they didn’t felt totally right to me. On the other hand, Perl regular expressions are a variant which has spread outside Perl world, likely because they gave something which was missing in basic or extended POSIX regexes.

Perl6 Raku is a totally new language, despite some syntactical and superficial resemblance with Perl5. With respect to the regular expression story, it takes it to a whole new level using a different syntax and introducing grammars which really make easy to write parsers.

Perl6 Raky has many other features, of course: it looks like a very interesting language.

Here I’m going to show something about it.

The language was renamed to Raku. I have edited this article consequently, except where file names are involved (that is, .p6 stays as .p6 instead of .raku.

Grammars

This is, I think, the most interesting feature (or, at least, the one interested me most so far). This feature makes it possible to easily define a grammar and parse something using that grammar.

I’ve tried to write a JSON grammar according to this, though I haven’t followed it strictly. It works, but it looks all wrong when and after several changes I started to like it, even compared to this one.

Here the link if you want to take a look at it.

I’ve also tried to write a grammar for an assembly language, but I did something totally wrong and the MoarVM went crazy. It should exist a way to avoid loops and infinite backtrackings. The use Grammar::Tracer didn’t help; indeed I think it made things worse. Instead

use Grammar::Debugger;

helped to spot the problem, as you can imagine.

Anyway, this grammar feature really makes it easy to do what otherwise could take a lot of time; I don’t know anything about afficiency, but I think it won’t matter provided you choose to use this feature when it is the right tool for the job…

Arguments from the command line

Why should it be so “hard” to take arguments from the command line? Perl6 Raku makes it easy like this:

sub MAIN(
    Str $file-name,        #= the file name to load
    Bool $make-it = False, #= should I make it?
)
{
    if $make-it {
        say "ok, doing something";
        my $buffer = $file-name.IO.slurp;
    }
}

The trailing comma in the argument list isn’t an error. You can leave it so that it will be easier to add other arguments to the list. When there’s a sub called MAIN, the signature says the arguments you have to provide from command line; if a default is given, the argument can be omitted. The comments (#=) document the arguments and are used for the help. If you run this without argument:

Usage:
  arg.p6 <file-name> [<make-it>]

    <file-name>    the file name to load
    [<make-it>]    should I make it?

These are positional arguments; but if you want the classical --argument thing:

sub MAIN(
    Str $file-name,         #= the file name to load
    Bool :$make-it = False, #= should I make it?
)
{
    if $make-it {
        say "ok, doing something";
        my $buffer = $file-name.IO.slurp;
    }
}

And the help will be:

Usage:
  arg.p6 [--make-it] <file-name>

    <file-name>    the file name to load
    --make-it      should I make it?

The “named” parameters come before the positional ones. If you want to mark a parameter as mandatory, append an exclamation mark, like:

sub MAIN(
    Str $file-name,         #= the file name to load
    Bool :$make-it = False, #= should I make it?
    Str :$something!,       #= This is mandatory
)
…

The help:

Usage:
  arg.p6 --something=<Str> [--make-it] <file-name>

    <file-name>          the file name to load
    --make-it            should I make it?
    --something=<Str>    and this too

Without the !, --something would be optional (hence surrounded by []). On the other hand, if you want to make <file-name> optional, you would write $file-name? in the signature.

When you need to take the rest of the arguments in an array, you add *@other-args (or whatever name you do prefer) as last parameter.

Since you can overload MAIN, you can have different signatures; e.g.

multi MAIN('sum', $a, $b) { say $a + $b }
multi MAIN('div', $a, $b) { say $a / $b }
multi MAIN('not', $a)     { say !$a }

It does what you imagine and the help is minimal, since I’ve omitted the comments:

Usage:
  arg.p6 sum <a> <b>
  arg.p6 div <a> <b>
  arg.p6 not <a>

As you can see I’ve omitted also the type, since it isn’t mandatory. Of course if you pass a string instead of a number, the sum will fail. If you specify the type Perl6 Raku will check it, and would give the help instead of trying to do the sum (failing).

You can add constraints, like these:

multi MAIN(Real $a, Real $b where $b > 0) { say $a / $b }
multi MAIN(Real $a, 0) { say "division by 0" }

Just an example, since of course you can solve this very same problem with a classical if. There’s a lot more in signatures, and of course this can be used also in the special MAIN sub.

Anyway…

This feature solves an annoying and recurring problem in a simple (from the point of view of usability) way: now you can enter your MAIN with all the arguments you need, and also you can document them and provide help in an easy and clean way.

Talking about constraints

Perl6 Raku isn’t strongly typed… if you don’t want to. But you can specify the type of your variable, if you need.

You can also define your new constrained type based on a basic type; like this:

subset Positive of Real where * > 0;

And now you can write:

multi MAIN(Real $a, Positive $b) { say $a / $b }

As far as I have seen Perl isn’t as rich as Ada in this particular game, but nonetheless I think these “constraint” features are a very good thing.

New operators

Having the possibility to define your own operators can boost expressiveness. In Perl6 Raku you can create infix operators and specify if they associate to left or right.

sub infix:<O>(Int $a, Int $b --> Int) is assoc<right> {
    $a² + $b
}

You can use it like this: 2 O 2 O 3, that is, 2 O (2 O 3) (11). Since it is an infix operator, you can use it with a shortcut syntax for reduce, that is:

    say [O] (2,2,3);

You may have noticed $a². Yes, you can write like that in Perl6 Raku. As off topic example, this is ok too:

    my $a = [O] (2,2,3);
    die "bug" if $a ≠ 11;

Lazyness

Perl6 can be lazy.

my @r = ^Inf;

This is a list with all values from 0 to infinity. You could use eager on it to stop Perl6 from being lazy…

say eager @r;

But this won’t work very well, as you can imagine. You can do the opposite: when Perl6 is eager to act, you can tell it to take it easy using the lazy keyword. For example:

my @r = lazy ^10;
say @r.is-lazy;

With just 10 values Perl6 doesn’t need to be lazy, unless you force it. However something like [O] @r would be calculated for real even if we won’t use the value.

my $s = [O] @r;
say $s.is-lazy;  # False

Here I don’t ask for the actual value of $s, so Perl6 could skip its calculation, which is very good if @r is big… On the other hand, it isn’t clear why I should calculate $s at all if I won’t use its value — in this case I made this example up just to show the result of $s.is-lazy.

Something more interesting with lazyness:

    my @g = ^∞ .map: *³;
    my @h = ^∞ .map: *²;
    my @w = lazy gather {
        loop {
            take @g.shift + @h.shift;
        }
    };
    say @w[5];

This is meaningless, but… it shows how using two infinite lists and an infinite loop can take a finite time when you are lazy… because you’ll never do more than necessary. When you ask for the element at index 5, all the previous values must be computed, because the gather needs to gather up to the sixth value, “consuming” @g and @h, in order to give the result.

If you remove the my @w = ... part and replace say @w[5] with

say @g[5] + @h[5];

the outcome is the same, and it should cost less. Moreover, the .shift modifies the lists (so, using it isn’t a good idea); therefore in the first approach, this is what happens:

say @g[0];  # OUTPUT: 0
# ... the code accessing @w
say @g[0];  # OUTPUT: 216

Values are, anyway, cached, so that @w[5] gives the same result… it can be also deduced from this:

    my ($t0, $t1);

    $t0 = now;
    say (0,1,2,3,4,5).map: {@w[$_]};
    $t1 = now;
    say $t1 - $t0;

    $t0 = now;
    say (5,4,3,2,1,0).map: {@w[$_]};
    $t1 = now;
    say $t1 - $t0;  # less than before

Conclusion

It’s a powerful language full of ideas and solutions…

No comments:

Post a Comment