About this ‘A Language a Day’ Advent Calendar
This article became a part of my book ‘A Language a Day’, which you can get in both electronic and paper format. Languages covered in the book: C++, Clojure, Crystal, D, Dart, Elixir, Factor, Go, Hack, Hy, Io, Julia, Kotlin, Lua, Mercury, Nim, OCaml, Raku, Rust, Scala, and TypeScript.
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 sub
s instead of method
s 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 await
ing 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