Raku at a Glance — A Language a Day, Advent Calendar 2019 Day 11/24

About this ‘A Language a Day’ Advent Calendar 2019

Welcome to Day 11 of this year’s A Language a Day Advent Calendar. Today’s topic is introduction to the Raku programming language.

Facts about the language

  • A re-thinking of Perl
  • Gradual type system
  • Multi-paradigm: object-oriented, functional
  • Allows concurrent computing
  • Announced in 2000, implemented in 2015
  • Renamed from Perl 6 in 2019
  • Website: raku.org

Installing and running Raku

To have Raku installed on your computer, download a package of Rakudo Star suitable for your operation system.

As of today, the command-line tool is called perl6, but it should be changed to raku with the next version of the above-mentioned compiler pack.

$ raku hello-world.raku

Hello, World!

The minimal program in Raku is very straightforward:

say 'Hello, World!'

The say built-in function does the same as print but adds a newline character at the end of the output.

Variables

You create a variable with the my keyword. Variable names in Raku are prefixed with the so-called sigils. If you have a single value in the variable, this is a scalar, and the sigil is $.

my $name = 'John';
say "Hello, $name!";

Variables that occur in the double-quoted strings are interpolated. Single quotes also denote a string, but no interpolation will be done there.

Aggregate types

Arrays are the containers that have the @ sigil.

my @data = (10, 20);
say @data[0];

Hashes (or associative arrays, or dictionaries in other languages’ terminology) are prefixed with a %.

my %mon = Jan => 'January', Feb => 'February';
say %mon{"Jan"}; # January
say %mon<Feb>;   # February

Notice the two different possible ways of accessing the hash elements.

Functions

Functions, or subroutines, are defined with the sub keyword. If the function accepts arguments, list them in parentheses. You can return something with the return keyword, otherwise the last computed value is the result of a function.

sub z($x, $y) {
    $x² + $y²
}

say z(3, 4); # 25

Notice how you can easily employ the Unicode characters in place of traditional ASCII constructions.

Default and optional parameters

Place a question mark after the parameter to make it optional. To assign a default value, simply ‘assign’ it in the function’s signature:

sub set-lang($lang = 'en') {
    say "Setting the language to $lang.";
    # . . .
}

set-lang 'fr';
set-lang;

Notice that you can often omit the parentheses when calling a function. Also notice that a hyphen is an accepted character in identifiers.

Type constraints

You can specify the type of both the parameters and the return value of a function. The following function takes and returns strings only. An attempt by passing an integer value will not be allowed by the compiler:

sub f(Str $s) returns Str {
    $s.uc
}

say f('Word'); # WORD
# say f(42); # Compile-time error

Raku also allows modifying its own grammar.

A Factorial example

Let us create something less ordinal, and define a factorial as an extension of the language grammar so that you can type 5! directly in your code.

sub postfix:<!>(Int $n where $n > 0) {
    [*] 1..$n
}

say 5!;   # 120
say 100!; # 9332621544…

The function has a special name, which makes it a part of the the Raku grammar. This subroutine will be triggered when you place the ! character after (that’s why postfix) the argument.

The function argument is made typed this time: Int $n. This is to prevent the function to be called with non-integer arguments. Here, an additional constraint is demanded from the function argument. You don’t need it, but if you have, an exception will be thrown if you will try to compute a factorial of a negative number: (-5)!.

Finally, the [*] construct is a use of the so-called reduction operator. In this case, it is applied to the range 1..$n, and the whole line is enrolled to make the following evaluation: 1 * 2 * 3 ** $n.

Lambdas

Use the pointer block to define a lambda. You can optionally add parameters to them. In the following example, two lambdas are created.

my $adder = -> $a, $b {$a + $b};
say $adder(3, 4); # 7

my $adder100 = -> $x {$adder(100, $x)}
say $adder100(10); # 110

To call such a function, simply apply the call postfix (...) to it (a postcircumfix operator if you want an exact name).

Classes

Classes are created with the class keyword. Data members are introduced with the has keyword, and the methods — with method. Here is an example of the class and of how it is instantiated:

class Person {
    has Str $.name;
    has Int $.age;

    method info() {
        "$!name is $!age years old."
    }
}

my $p = Person.new(name => "John", age => 22);
say $p.info; # John is 22 years old.

The object is created using the new method of the class. In this example, the data members are additionally type restricted.

Notice the second character after the $ sigil. When it is a dot, Raku creates a getter and a setter for such variable. Inside the method, use ! instead of a dot to have direct access to a data member without calling a generated getter. If you declare a variable with an exclamation mark as has $!name, for example, it will be considered private, and you cannot access it from outside of the class.

Class-level data members

It is also possible to have data members on the class level, or static, or state members if we use other languages’ terminology. You can define them with my instead of has, and you can also use subs instead of methods to create class-level methods (they will obviously have no access to object’s data).

class Counter {
    my $total = 0;

    has $.my-number = ++$total;

    method info() {
        "My number is $!my-number.";
    }
}

my $c1 = Counter.new;
my $c2 = Counter.new;
my $c3 = Counter.new;

say $c1.info; # My number is 1.
say $c2.info; # My number is 2.
say $c3.info; # My number is 3.

Hierarchy

The simplest way to create a child class is mentioning the base class after the keyword is. You can also work with the so-called traits, which are similar to interfaces in other languages. In the next example, the classical hierarchy is used.

A Polymorphic example

class Animal {}

class Cat is Animal {
    method info() {"I am a cat"}
}

class Dog is Animal {
    method info() {"I am a dog"}
}

my @zoo of Animal = Cat.new, Dog.new, Cat.new, Dog.new;
for @zoo -> $x {    
    say $x.info;
}

The @zoo array is declared as of Animal to make sure it only keeps the objects of the Animal type or its children. The call of $x.info is dispatched to the method defined in the correct class.

Concurrent computing

In Raku, there are ways to organise concurrent and asynchronous computing. You can browse the documentation and learn about promises, supplies, channels, threads, schedules, etc. Let me briefly tell about a simple way to organise an asynchronously executed block of code.

print 'Hello, ';

my $job = start {
    sleep 0.5;
    say 'Hi there from a job!';
}

say 'World!';
await $job;

This program creates a promise. It is returned by the start routine. As soon as it is created, the main code continues. A bare start is an equivalent of a more verbose form of calling the Promise.start constructor.

So, the program first prints the two parts of the phrase Hello, World! and, after a small delay, the message from the block. To see the message, we need to wait until the code block finishes execution, thus we are awaiting for the $job.

Sleep Sort

We can use start to implement the Sleep sort algorithm.

my @data = 10, 4, 2, 6, 2, 7, 1, 3;

await @data.map: {start {
    sleep $_ / 10;
    .say;
}};

For each data item, a separate promise is created. Its code sleeps for some time, proportional to the value of the current number, and then prints it. To wait for all tasks to finish, await is applied to the sequence of promises that is returned by the map method. Notice the colon notation which allows to make the code a little bit freer from punctuation characters.

Grammars

In this series, I am trying not to solve the predefined list of tasks only, but also tell about some unique aspects of the language. With Raku, these are grammars.

Grammars are the next level of regular expressions organised in the shape of classes. and allow you — on a bigger scale — to write compilers completely in Raku. Let us look at a simple example of how you can parse the URL structure using grammars.

grammar URL {
    token TOP {
        <protocol> '://' <host> <path>?
    }

    token protocol { 'http' | 'https' }
    token host     { [\w+]+ %% '.' }
    token path     { '/' [\w+]* %% '/' }
}

A grammar consists of a set of rules and tokens, each of them are regular expressions that can refer to other rules or tokens. The URL grammar describes how the URL is built out of its parts. It is a protocol name, followed by a literal character sequence ://, followed by a host name and an optional path (for simplicity, I intentionally omitted the query string and the hash-prefixed fragment).

The parts of an URL are then defined in their own token methods. For example, a protocol is either an 'http' or 'https' strings. The host and the path are similar to each other, and they both are word-character sequences separated by '.' or '/'.

You can apply this grammar to a string containing an URL and it will be split to its structural parts according to the definition.

say URL.parse("https://www.google.com");
say ~URL.parse("https://www.google.com/search/images")<path>;

The first line prints the whole parse tree:

「https://www.google.com」
 protocol => 「https」
 host => 「www.google.com」

In the second line, a specific part (path) is taken, converted to a string (by the ~ prefix) and printed: /search/images.

Get more

Raku is much more than it was possible to share in a single article. I advise you to visit its website and read the most recent freely available Raku books:

On GitHub, you can find the complete source codes of today’s examples.

Next: Day 12. Elixir

Leave a Reply

Your email address will not be published. Required fields are marked *