100 Languages Speedrun: Episode 26: Raku (Perl 6)

Raku, which started its life as Perl 6, was meant as successor to Perl language.

The main problem with that is that Perl 6 was officially announced in 2000, and only properly appeared in 2015, and in that time frame Perl lost most of its popularity, with most people moving to Ruby, PHP, or Python.

Raku isn't trying to achieve compatibility with Perl 5, but it follows very similar style. This probably seemed like a good idea back in 2000 when Perl was very popular, considerably less so nowadays.

But in addition to just being cleaned up Perl 5, Raku also has some really cool new concepts that are definitely worth taking a look at.

Hello, World!

#!/usr/bin/env raku

say "Hello, World!";

Two things to notice here.

The good thing - say is a really nice name. Of all the words languages use for this concept, I think say is much better than print or puts or console.log or println or prn or writeln and so on. Raku uses a lot of new names for various functions, some quite good, some not so much.

The bad thing - damn mandatory semicolons. Somehow Raku didn't drop this nonsense, even though they've been dropped by nearly every modern language by now, as well as by all good JavaScript coding styles (if you're coding JavaScript with semicolons, you're doing it wrong).

Every useless token increases mental load, and every pointless "missed semicolon" error costs both programmer time and concentration - extremely limited resources. There's no excuse to force semicolons in any new language.

Semicolons in Perl and Raku are statement separators not terminators, so unlike C/Java/etc. you can skip them for last statement of a block.

FizzBuzz

#!/usr/bin/env raku

sub fizzbuzz($n) {
  given $n {
    when $_ % 3 == 0 && $_ % 5 == 0 { "FizzBuzz" }
    when $_ % 3 == 0 { "Fizz" }
    when $_ % 5 == 0 { "Buzz" }
    default { $_ }
  }
}

for 1..100 {
  say fizzbuzz($_)
}

Here's one way to do FizzBuzz. The loop is very nice, and 1..100 for range from 1 to 100 makes much more sense than range(1, 101) convention.

Raku just like Perl uses $_ for default variable in a lot of contexts, such as for loops and given blocks. Just to make clear - these two $_s are not overwriting each other, each $_ is specific to its block.

Fibonacci

#!/usr/bin/env raku

multi sub fib(1) { 1 }
multi sub fib(2) { 1 }
multi sub fib($n) { fib($n-1) + fib($n-2) }

for 1..30 {
  say fib($_)
}

Raku allows defining multiple functions with the same name, matched depending on number of parameters, their types, or values. This looks quite Haskell-like.

Equality

Time to get to the most shitty part of Perl design, that 100% got copied over to Raku. There are no "numbers" or "strings". It's just scalars, and they convert automatically.

But it's not so simple. 2 and 2.0 are the same thing (as numbers), but "2" and "2.0" are completely different (as strings). So Perl and Raku have full parallel sets of operators. For some like concatenation (2 ~ 2 is 22, + is always numerical) that's fine, but the absolute worst is that there are two equalities: == for numbers and eq for strings.

What's the problem with that? Well, in a normal language with normal == operator, it can be used not just for numbers and strings, but also for arrays, hashes, sets, or whatever else we throw at it.

In Raku we have this abomination:

#!/usr/bin/env raku

# Numerical
say 2 + 2 == 4;
say 2 == "2.0";

# String equality
say "hello" eq "hello";

# Looks like it works...
say [1, 2] eq [1, 2];
# Raku sorts the key order (unlike Ruby) so they match
say {b=>2, a=>1} eq {a=>1, b=>2};

# WTF? How is any of these True?
say ["one", "two"] eq "one two";
say [1, 2] eq ["1 2"];
say [1, 2] eq "1 2";
say {a=>1} eq "a\t1";
say {a=>1, b=>2} eq "a\t1\nb\t2";

It's True for every one of them.

Also notice completely insane default stringification rules (Str()) - instead of Str([1, 2]) being something sensible like [1, 2], it's "1 2". And for hashes, what the hell with those tabs and newlines?

JSON

There's another popular language with broken inequality - JavaScript. And the traditional workaround would be to JSON.stringify(a) === JSON.stringify(b).

Let's see how this goes. First Rakudo comes without JSON in standard library, and without package manager.

I needed to brew uninstall rakudo moarvm nqp and brew install rakudo-star instead to get its zef package manager. That is really weird, but a lot of languages have packaging quirks, so let's roll with it. I didn't actually need to install any packages, as rakudo-star package (but not rakudo package) already included JSON::Tiny.

But wait a minute, if Raku doesn't distinguish numbers from strings, how is it even doing JSON at all!

#!/usr/bin/env raku

use JSON::Tiny;

say to-json([1, 2]);
say to-json(["1", "2"]);

It turns out this works just fine:

$ ./json.raku
[ 1, 2 ]
[ "1", "2" ]

Raku acts as if there was no distinction between numbers and strings, but it's there behind the scenes internally. Number 2 and string "2" are 99% same, except when they aren't.

I still remember this bug from my Perl days, where printing a variable would change its type:

#!/usr/bin/perl -l

use JSON;

my $x = 2;
print encode_json($x);
print "$x";
print encode_json($x);

Which led to this WTF:

$ ./json_bug.pl
2
2
"2"

So you'd print something for debugging, and suddenly your program would work completely differently. That was a fun evening, I still remember it 15 years later, and that issue was buried much deeper than this.

Anyway, I cannot reproduce it in Raku:

#!/usr/bin/env raku

use JSON::Tiny;

my $x = 2;
say to-json($x);
say "$x";
say to-json($x);

Where it definitely works:

$ ./json_bug.raku
2
2
2

But we'll probably still run into the issue where numbers and strings are 99% same except where they aren't. Maybe more experienced Raku programmers can tell me how to trigger this issue in Raku.

Oh and notice kebab-case identifiers are supported.

JSON Equality

Let's try our equality-by-JSON:

#!/usr/bin/env raku

use JSON::Tiny;

sub json-equal($a, $b) {
  to-json($a) eq to-json($b)
}

# True as expected
say json-equal([1, 2], [1, 2]);

# False as expected
say json-equal([1, 2], "[1, 2]");
say json-equal("2", "2.0");

# Also True...
say json-equal(5, 5.0);
say json-equal(0, -0);

# Also False...
say json-equal(1, "1");

# Totally random if True or False
say json-equal({b=>2, a=>1}, {a=>1, b=>2});

First three tests are fine. Then 5 and 5.0 are same, as are 0 and -0 - because these are actually big rationals not floats, let's not get into Raku number system just yet.

1 and "1" being not JSON-equal is something we already knew about.

But what about {b=>2, a=>1} and {a=>1, b=>2}? Str() always sorted their keys so they were always eq, but for JSON conversion their order is not guaranteed, so this program will print True or False at random.

Overall, total disaster. This can be worked around, but from my Perl and JavaScript experience, not having working universal == is massive pain that comes up all the time.

EQV

Oh wait, we're not done yet. There's eqv! Let's see how it does:

#!/usr/bin/env raku

# True as expected
say [1, 2] eqv [1, 2];
say {b=>2, a=>1} eqv {a=>1, b=>2};

# This is still True
say 0 eqv -0;

# False as expected
say [1, 2] eqv "[1, 2]";
say "2" eqv "2.0";

# Now this is False too?
say 5 eqv 5.0;
say 1 eqv "1";

It's the closest to what we got so far. 1 and "1" being different is what we already knew about. But now 5 and 5.0 are treated by eqv as different (as integer and rational), even though they were the everywhere else up to this point. It's still the best we got so far, but it's a total embarrassment to design a language without a working universal == in this century.

Unicode

At least Perl never had problems with Unicode, and neither does Raku:

#!/usr/bin/env raku

say uc "Żółw";
say chars "Żółw";
say chars "🍰";

Which outputs completely correct:

$ ./unicode.raku
ŻÓŁW
4
1

State and Hashes

Let's do Fibonacci again, except with memoization:

#!/usr/bin/env raku

sub fib($n) {
  state %cache = (0 => 0, 1 => 1);
  %cache{$n} //= fib($n - 1) + fib($n - 2);
}

say fib($_) for 1..1000

Raku has a lot of scopes. my are the usual local variables. state are scoped to their function, but they're only initialized once.

Variable types can be inferred by the sigil - $ are scalar (strings, integers, and so on). @ are arrays. % are hashes (or dictionaries). Because Raku knows their type, it can initialize them to correct empty values - but i this case we initialize it ourselves.

A //= B sets A to B if it's missing (||= checks truthiness of A, and that's not what we want, as 0, empty string etc. are false in Raku). It also returns the return value.

So this is extremely concise way to do memoization for a language that doesn't have builtin support for memoization specifically.

Oh and you can use if, for and such as a suffix. That definitely improves readability for "guard clauses" (return if ... or return unless or break if ...), not necessarily here with a for loop.

Junctions

Did you ever want to write if x == 1 | 2 | 3, but your language wouldn't support it, so you needed to do if x == 1 || x == 2 || x == 3 instead?

I've got some good news!

#!/usr/bin/env raku

say "What's your name?";
my $name = get;

if ($name eq "admin" | "root") {
  say "Don't try to hack me"
} else {
  say "Hello, $name";
}

Let's give it a go:

$ ./junctions.raku
What's your name?
Alice
Hello, Alice
$ ./junctions.raku
What's your name?
admin
Don't try to hack me

get reads one line, and | creates a "junction". It's a collection of values in one of the modes (and, or, one, none) that performs all its operations on each element for as long as it can.

#!/usr/bin/env raku

say "Some of them match!" if (1 | 2 | 3) * 2 == (4 | 5);
say "All of them match!" if (1 & 2) * 2 == (4 | 5 | 2);
$ ./junctions2.raku
Some of them match!
All of them match!

Only when evaluated in boolean context junctions collapse to a single value. The code means:

  • one of (1 * 2, 2 * 2, 3 * 2) equals one of (4, 5)
  • all of (1 * 2, 2 * 2) equals one of (4, 5, 2)

Junctions are pretty much unique to Raku, and I have no idea if they actually make coding easier, or they're just unnecessary complexity.

Regular expressions

Perl introduced its own extended regular expressions, and now every single language uses Perl style regular expressions, so it was a huge success. The pre-Perl version is basically forgotten. Perl's regular expressions were at least largely compatible with pre-Perl kind, so they were limited to using small number of symbols in some twisted combinations to achieve extra functionality.

Raku obviously introduced its own regular expression system, which tries to be more expressive and is intentionally not backwards compatible.

#!/usr/bin/env raku

say "What's your name?";
my $name = get;

if ($name ~~ /^<[a..z]>+$/ | /^<[A..Z]>+$/) {
  say "Hello, $name"
} else {
  say "Please use consistent case"
}

What is going on here...

$ ./regexp.raku
What's your name?
alice
Hello, alice
$ ./regexp.raku
What's your name?
BOB
Hello, BOB
$ ./regexp.raku
What's your name?
Charlie
Please use consistent case

First, the pattern matching with ~~ (Perl and Ruby use =~). Any junction for |. Neither is specific to regular expressions.

Interesting things start with /^<[a..z]>+$/. Raku regular expressions has a lot of new features, and so they had to change some existing syntax to make space for all the new features. Character range [a-z] became <[a..z]>. The [] got repurposed to be non-capturing group ((?:) in Perl regexp), which is arguably much more important functionality.

The subject of Raku Regular Expressions is big enough, that I'll need to dedicate a whole episode to it. It's basically a language within a language.

Unicode operators

It really should come at no surprise that Raku has Unicode operators in its syntax. After Julia, this is the second language we're investigating which does it, and I expect this to become mainstream feature of new programming languages going forward.

Porting Julia's example:

#!/usr/bin/env raku

say [1,2] ⊆ [2,3,1];
say 3 ∈ [2,3,1];
say (π/3).sin * 3.sqrt;

It outputs:

$ ./unicode_operators.raku
True
True
1.4999999999999998

Just like in Julia, set operations like and work the same. π is the usual constant. Unlike Julia, .sin is a method not a standalone function, and .sqrt is a method and there's no .

Neither Raku nor Julia go overboard with Unicode operators, but they can definitely make math code more readable.

Kitchen Sink Language Design

Raku is a kitchen-sink language like Perl or Scala. They tend to try out all the ideas, courageously break with pass assumptions, and some of these turns out to be amazing. But all the not-so-good ideas have high mental cost, so kitchen-sink languages tend to top the charts of the most disliked as well.

Sometimes it works wonders. Perl might have fallen far from its peak popularity, but it will live forever as the ancestor of pretty much every modern programming language. Ruby, JavaScript, PHP (and to lesser degree Python) are all basically Perl's children, and these languages in turn influenced everything else. It revolutionized programming language design. Just compare the last major pre-Perl language Java, which is a total usability disaster, with any of the post-Perl designs.

On a smaller scale CoffeeScript tried to throw a kitchen sink at making JavaScript bearable, and while it's largely dead now, ES6+ wouldn't exist without CoffeeScript paving the way.

But just as often, it doesn't work at all. Kitchen sink languages like Raku or Scala didn't get far, and so far didn't have much impact on other languages either.

Should you use Raku?

If you're interested in programming language design, or thinking about designing your own programming language, Raku is definitely worth checking out, even if you don't plan to write any real code in it. It tries out so many things, I barely scratched the surface here.

If you're a Perl programmer who for some reason doesn't want to switch to Ruby or Python like most, Raku might be worth a try. For me it hold on to too many bad design ideas from Perl, but if you're already accepting the costs, maybe it's not a big deal.

On the other hand, if you think Ruby is already Perl 6, or can't stand Perl style languages, then Raku will be highly unappealing.

I haven't really explored how well Raku works in pragmatic terms, such as its ecosystem, performance, and so on. From a quick look it doesn't look too great.

I'd definitely recommend Raku as language to give a go for a fun weekend, even if few would want to use it for anything more serious.

Code

All code examples for the series will be in this repository.

Code for the Raku episode is available here.