Raku Is the Most Advanced Scripting Language
A while back I wrote about why Raku has yet to catch on in the mainstream. Nothing in that post was about the language itself, and I want to be clear about what I think of the language itself: Raku is the most advanced scripting language ever created.
Not the most popular.
Probably not the one you'll get to use at work anytime soon.
The most advanced.
What follows in this post is mostly code. I ran all of it on Rakudo v2026.06.
The normal parts#
Let's start with the normal parts.
Variables carry a sigil that tells you the shape of the thing.
my $name = 'Greg';
my @langs = <raku perl ruby>;
my %ages = greg => 51, ada => 36;
say "$name writes @langs[0] and has %ages<greg> reasons";
# Greg writes raku and has 51 reasons
Interpolation runs code inside braces, so you rarely need to concatenate anything.
my @nums = 1..5;
say "sum: { @nums.sum }, list: @nums[]";
# sum: 15, list: 1 2 3 4 5
Types are optional, but they're checked if you use them. Named parameters can take defaults.
sub greet(Str $who, Str :$greeting = 'Hello') { "$greeting, $who!" }
say greet('world'); # Hello, world!
say greet('Raku', greeting => 'Howdy'); # Howdy, Raku!
Multiple dispatch picks the candidate by signature. You can dispatch on a literal value or on an arbitrary where constraint, so the base cases and the recursive case become three separate subs.
multi fib(0) { 0 }
multi fib(1) { 1 }
multi fib(Int $n where * > 1) { fib($n - 1) + fib($n - 2) }
say (^10).map(&fib);
# (0 1 1 2 3 5 8 13 21 34)
List processing is what you'd expect, with * standing in for the argument you didn't bother naming.
say (1..20).grep(* %% 3).map(* ** 2);
# (9 36 81 144 225 324)
my @words = <pear Apple banana cherry>;
say @words.sort(*.lc);
# (Apple banana cherry pear)
%% is the divisibility operator. Raku provides this because we all write x % y == 0 many times per day. There's a negated form too, so the leap year rule reads about like an English sentence.
say 10 %% 5; # True
say 10 %% 3; # False
say 10 % 3; # 1
sub leap-year($y) { $y %% 4 && $y !%% 100 || $y %% 400 }
say (1900, 2000, 2024, 2026).map(&leap-year);
# (False True True False)
Hashes iterate as Pair objects. The -> can be used to add a block as a signature, and then writing (:$key, :$value) in that signature takes the pair apart, so you get two variables instead of one pair, where you would then need to use .key and .value.
my %stock = apples => 12, pears => 3, plums => 0;
for %stock.sort(*.key) -> (:$key, :$value) {
say "$key: $value";
}
# apples: 12
# pears: 3
# plums: 0
say %stock.grep(*.value > 0).map(*.key).sort;
# (apples pears)
Strings and sprintf behaviors:
my $line = ' Raku, Perl, Ruby ';
say $line.trim.split(/\s* "," \s*/); # (Raku Perl Ruby)
say 'raku'.tc, ' ', 'raku'.uc, ' ', 'Raku'.flip; # Raku RAKU ukaR
say sprintf('%-10s|%5.2f|', 'total', 3.14159); # total | 3.14|
Raku regexes are not Perl regexes. The syntax was redesigned instead of inherited, so a pattern you know from Perl or PCRE will likely need small edits. Whitespace inside a pattern means nothing. Literals get quoted, and captures are numbered from zero, like other lists.
if 'version 2026.06' ~~ / (\d ** 4) '.' (\d ** 2) / {
say "year $0 month $1";
}
# year 2026 month 06
say '2026-08-10'.subst(/(\d+)\-(\d+)\-(\d+)/, { "$1/$2/$0" });
# 08/10/2026
Classes give you accessors from the sigil.
A . twigil is public.
A ! twigil is private.
A $!a is how you access the attribute directly from inside the class.
Defining gist is how you decide what say prints.
class Triangle {
has Numeric $.a = 0;
has Numeric $.b = 0;
method hypotenuse(--> Numeric) { sqrt($!a² + $!b²) }
method gist { "right triangle with legs $!a and $!b" }
}
my $t = Triangle.new(a => 3, b => 4);
say $t; # right triangle with legs 3 and 4
say $t.a, ' and ', $t.b; # 3 and 4
say 'hypotenuse ', $t.hypotenuse; # hypotenuse 5
That is a superscript 2 doing the exponentiation. More on that to come.
Roles are the composition mechanism. They get flattened into the class when it's composed instead of sitting out in a lookup chain, which is what I like about them.
role Loud { method speak { self.sound.uc ~ '!!!' } }
class Dog does Loud { method sound { 'woof' } }
say Dog.new.speak; # WOOF!!!
Files are objects with methods on them.
my $tmp = $*TMPDIR.add('example.txt');
$tmp.spurt("one\ntwo\nthree\n");
say $tmp.lines.elems, ' lines, longest is ', $tmp.lines.max(*.chars);
# 3 lines, longest is three
$tmp.unlink;
Decimal literals are rationals.
say 1/3 + 1/3 + 1/3 == 1; # True
say 0.1 + 0.2 == 0.3; # True
say (1/3).WHAT, ' ', (1/3).nude; # (Rat) (1 3)
say 2 ** 100; # 1267650600228229401496703205376
Integers are arbitrary precision. A Rat keeps its numerator and denominator around until something forces it to a float, and .nude gives you both of them. Every language eventually gets asked about 0.1 + 0.2. Raku's answer is to not store decimal literals in binary floating point format.
Command line scripts get argument parsing from the signature. You can declare MAIN, annotate it with #| and #= comments, and you get the usage message for free.
#| Deploy a release to a host
sub MAIN(
Str $host, #= the target host
Int :$port = 22, #= ssh port
Bool :$dry-run = False, #= print, do not run
) {
say "would deploy to $host:$port" ~ ($dry-run ?? ' (dry run)' !! '');
}
$ raku deploy.raku --port=2222 --dry-run example.com
would deploy to example.com:2222 (dry run)
$ raku deploy.raku
Usage:
deploy.raku [--port[=Int]] [--dry-run] <host> -- Deploy a release to a host
<host> the target host
--port[=Int] ssh port [default: 22]
--dry-run print, do not run [default: False]
I didn't write the usage block and I didn't import anything to get it working either. The only thing to remember is that named options have to come before positional options.
So that's a much better than average scripting language with a solid type system and no floating point surprises.
The more exotic parts#
A junction holds several values "at once". If you compare against it, the comparison runs against each value, then returns a single True or False.
my $n = 42;
say 'divisible' if $n %% (2 | 3 | 7);
# divisible
sub is-prime($x) { $x > 1 && $x %% none(2 .. $x.sqrt.Int) }
say (1..30).grep(&is-prime);
# (2 3 5 7 11 13 17 19 23 29)
The predicate says "divisible by none of two through the square root," which is the definition of a prime number. No loop, no flag variables, no early returns.
Metaoperators build new operators out of the ones you already have.
[ ] reduces.
Z zips.
X crosses.
» and « push an operator out across lists
[\ ] gives you the running partial results.
say [+] 1..100; # 5050
say [*] 1..10; # 3628800
say [max] <3 17 4 9>; # 17
say (1..5) Z (<a b c d e>); # ((1 a) (2 b) (3 c) (4 d) (5 e))
say (1..3) X ('a', 'b'); # ((1 a) (1 b) (2 a) (2 b) (3 a) (3 b))
say <1 2 3> »+» 10; # (11 12 13)
say [1,2,3] »*« [4,5,6]; # [4 10 18]
say [\+] 1..6; # (1 3 6 10 15 21)
None of those are library functions. [+] is the + operator with a metaoperator around it, and it will do the same thing to an operator you write yourself.
The sequence operator works out the pattern from the elements you give it, including rules written as closures over the previous terms. A * on the right means keep going forever, which is safe, because the list is lazily generated.
say (1, 2, 4 ... 512); # (1 2 4 8 16 32 64 128 256 512)
say (1, 1, * + * ... *)[^15]; # (1 1 2 3 5 8 13 21 34 55 89 144 233 377 610)
my @fib = 1, 1, * + * ... *;
say @fib.first(* > 1_000_000); # 1346269
say @fib[^5]; # (1 1 2 3 5)
@fib is the whole Fibonacci sequence, in an array, indexable, but not computed yet. gather and take build a lazy list out of ordinary control flow, so a loop with no upper bound is fine as long as you only ask for a few of them.
my @primes = lazy gather for 2..* { .take if $_ %% none 2..$_.sqrt };
say @primes[^10];
# (2 3 5 7 11 13 17 19 23 29)
say (^Inf).map(* ** 2).first(* > 1000); # 1024
Operators are subs with unusual names, so you can write your own. Prefix, infix, postfix, and circumfix are all available. The parser picks them up at compile time.
sub infix:<±>($a, $b) { $a - $b, $a + $b }
say 10 ± 3; # (7 13)
sub postfix:<!>($n) { [*] 1..$n }
say 20!; # 2432902008176640000
sub prefix:<√>($x) { $x.sqrt }
say √256; # 16
Factorial is now a postfix operator whose body is a reduction over a range, and it's spelled the same way it's spelled in math class.
A subset is a type defined by a predicate, checked at binding time like any other type.
subset Port of Int where 1 .. 65535;
sub listen(Port $p) { "listening on $p" }
say listen(8080); # listening on 8080
say (try listen(70000)) // 'rejected 70000'; # rejected 70000
A port number is an integer from 1 to 65535. Now the type system knows that, and the check happens at the boundary instead of forty lines into the function.
Any object can take on a role at runtime with but, without touching its class.
my $dog = 'rex' but role { method bark { 'woof' } };
say $dog.bark, ' from ', $dog; # woof from rex
Phasers run code at particular moments in a block's lifetime:
ENTER, LEAVE, FIRST, LAST, BEGIN, END, and several others.
You can put them anywhere in the block, because the name says when they run, not their location.
sub counted {
ENTER say 'entering';
LEAVE say 'leaving';
state $calls = 0;
say 'call ', ++$calls;
}
counted() for ^2;
# entering
# call 1
# leaving
# entering
# call 2
# leaving
A grammar is a class whose methods are named regexes. Those regexes call each other, so the parser for a format is a set of small named rules instead of an unreadable pattern. An actions class has a make for each match, and a data structure is returned (instead of a match object).
grammar INI {
token TOP { <section>+ }
token section { '[' $<name>=(\w+) ']' \n <pair>* }
token pair { $<key>=(\w+) \h* '=' \h* $<value>=(\N+) \n? }
}
class INI::Actions {
method TOP($/) { make $<section>.map({ .made }).Hash }
method section($/) { make ~$<name> => $<pair>.map({ .made }).Hash }
method pair($/) { make ~$<key> => ~$<value> }
}
my $ini = q:to/END/;
[server]
host = example.com
port = 8080
[log]
level = debug
END
say INI.parse($ini, actions => INI::Actions.new).made;
# {log => {level => debug}, server => {host => example.com, port => 8080}}
I parse HAML this way in Template::HAML. Grammar.rakumod holds the tokens for tag names, shorthand classes and ids, attribute lists, and filters, and Actions.rakumod turns each match into a node the renderer walks.
Also, Grammars inherit, so you can subclass a parser and override one token in it. Rakudo parses Raku with a grammar of its own, Raku::Grammar, which subclasses HLL::Grammar and pairs with a Raku::Actions class. It is written in NQP, the Raku subset Rakudo bootstraps from, rather than in full Raku.
Concurrency has real primitives instead of a callback convention. start returns a Promise, await collects them, and Supply, react, and whenever handle values arriving over time.
my @squares = await (1..4).map: -> $i { start { $i ** 2 } };
say @squares; # [1 4 9 16]
my $sup = Supply.from-list(1..5).map(* * 10);
react { whenever $sup -> $v { print "$v " } } # 10 20 30 40 50
.race and .hyper parallelize a list pipeline by adding one word to it.
say (^20).race.map(* ** 2).sum; # 2470
Sets, bags, and mixes are built-in types with their own operators.
my $a = set <apple pear plum>;
my $b = set <pear plum fig>;
say $a (&) $b; # Set(pear plum)
say $a (|) $b; # Set(apple fig pear plum)
say 'pear' ∈ <apple pear plum>; # True
say (1..5) ⊆ (1..10); # True
say bag(<a b a c a b>).sort; # (a => 3 b => 2 c => 1)
The metaobject protocol, or MOP, is a public API. You can ask a class about itself and you can change it while the program is running.
class Cat { }
Cat.^add_method('meow', method { 'meow' });
Cat.^compose;
say Cat.new.meow; # meow
say Cat.^can('meow') ?? 'yes' !! 'no'; # yes
say Int.^mro; # ((Int) (Cool) (Any) (Mu))
Subs can be wrapped in place. You can add tracing without editing the sub.
sub slow-thing { 'result' }
&slow-thing.wrap(-> |c { "[traced] " ~ callsame() });
say slow-thing(); # [traced] result
A Proxy is a container with a FETCH and a STORE on it, so a variable can be a view onto another variable.
my $fahrenheit = 212;
my $celsius := Proxy.new(
FETCH => method { ($fahrenheit - 32) * 5 / 9 },
STORE => method ($c) { $fahrenheit = $c * 9 / 5 + 32 },
);
say $celsius; # 100
$celsius = 37;
say $fahrenheit; # 98.6
Feed operators point in the direction the data is going.
my @out;
(1..20) ==> grep(* %% 2) ==> map(* ** 2) ==> sort() ==> @out;
say @out; # [4 16 36 64 100 144 196 256 324 400]
.rotor cuts a list into chunks, with an optional overlap, and .classify builds the hash-of-lists much easier than doing it manually.
say (1..10).rotor(3); # ((1 2 3) (4 5 6) (7 8 9))
say (1..10).rotor(3 => -1); # ((1 2 3) (3 4 5) (5 6 7) (7 8 9))
say <apple avocado banana blueberry cherry>.classify(*.substr(0,1));
# {a => [apple avocado], b => [banana blueberry], c => [cherry]}
Calling into C is a just a module and a signature.
use NativeCall;
sub getpid(--> int32) is native {}
say getpid() == $*PID; # True
Then there's the Unicode. Raku source code is UTF-8, identifiers can be nearly anything. A lot of operators have both an ASCII and a non-ASCII spelling, and superscripts and common fractions are numeric literals.
my $π = 3.14159;
say $π; # 3.14159
say 3² + 4²; # 25
say (½ + ⅓).nude; # (5 6)
say (1..5).map({ $_² }); # (1 4 9 16 25)
½ + ⅓ comes out to five sixths, exactly.
Yes, some of these may be painful to type, but they parse, and I'd much rather have the option and not need it.
So what?#
I know a lot of programming languages and I'm familiar with many others. Every feature I've mentioned here exists in some other programming language:
Grammars are a parser generator: ANTLR, bison.
Junctions are a fold over a comparison: Python, Ruby, SQL.
Lazy lists are generators: Python, JavaScript, Clojure.
The metaobject protocol is reflection: CLOS, Smalltalk, Java, Ruby, Python.
A Proxy is a property: Python, JavaScript.
And if you're old like me, you can probably think of even more.
The thing that makes Raku special is having all of these language features in one language, in one cohesive syntax.
So that's my case for "most advanced scripting language".
Interested?#
The Raku compiler is at rakudo.org.
The Raku docs are at docs.raku.org.
And the Raku modules are at raku.land.
🤘