Raku Is the Most Advanced Scripting Language Ever Created
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: Raku is the most advanced scripting language ever created.
Not the most popular.
Probably not the one you'll get to use at work.
The most advanced.
What follows is mostly code. I ran all of it on Rakudo v2026.06.
The normal parts#
Start with the boring 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
Signatures are real. Types are optional, but they're checked when 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 and 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 feel like 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 has one because everybody writes x % y == 0 about four times a day. There's a negated form too, so the leap year rule reads about like the sentence does.
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 -> on a block starts a signature, and writing (:$key, :$value) in that signature takes the pair apart for you, so you get two variables instead of one pair to call .key and .value on.
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 behave.
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 5 regexes. The syntax was redesigned instead of inherited, so a pattern you know from Perl or PCRE will often need small edits. Whitespace inside a pattern means nothing, literals get quoted, and captures are numbered from zero like every other list in the language.
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, and $!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 at the end.
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.
Command line scripts get argument parsing from the signature. Declare a 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 that usage block and I didn't import anything to get it. The one thing to remember is that named options have to come before positionals.
That's a better than average scripting language with a solid type system and no floating point surprises. Here's the rest of it.
The exotic parts#
A junction is one value that is several values at the same time, and it collapses when you compare against it.
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 variable, no early return.
Metaoperators build new operators out of the ones you already have. [ ] reduces, Z zips, X crosses, » and « push an operator out across lists, and [\ ] 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 a rule written as a closure over the previous terms. A * on the right means keep going forever, which is fine, because the list is lazy.
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, and 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, and 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 way it's spelled on paper.
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 life. 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 the position.
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 one unreadable pattern. An actions class hangs a make on each match, and what comes back is a data structure 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}}
Grammars inherit, so you can subclass a parser and override one token in it. Rakudo parses Raku with a Raku grammar.
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, spelled both ways.
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, which is how you 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, which beats reading a long pipeline inside out.
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 overlap if you ask for one, 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 module and a signature.
use NativeCall;
sub getpid(--> int32) is native {}
say getpid() == $*PID; # True
Then there's the Unicode. Source is UTF-8, identifiers can be nearly anything, a lot of operators have both an ASCII and a non-ASCII spelling, and superscripts and vulgar 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. I don't type any of these on purpose, but they parse, and I'd rather have the option and not need it.
So what?#
Every feature I've mentioned exists somewhere else, in some other programming language. Grammars are a parser generator. Junctions are a fold over a comparison. Lazy lists are generators. The MOP is reflection. A Proxy is a property. The thing that makes Raku special is having all of them in one language, in one syntax, with no build step, in a file you can run like raku myfile.raku.
That's my case for "most advanced". It is not a case for most used... I already wrote that sad post.
In the meantime: my blog, my web framework, my ORM, my templating language, and my test library are all Raku, and I'm going to keep writing Raku whether or not anyone shows up.
If you're interested, the compiler is at rakudo.org, the docs are at docs.raku.org, and the modules are at raku.land.
🤘