What It Set Out to Change — A Language Where 0.1 + 0.2 == 0.3 Is True

In Raku, 0.1 + 0.2 == 0.3 returns True, because decimal literals are rationals by default. That one line captures what Perl 6 prioritised: not surprising you when you write the obvious thing. Sigil invariance, grammars, multiple dispatch, meta-operators, junctions, lazy lists. Lay them out and it becomes clear — each is a language's worth of work. Half the reason it took fifteen years is right here.

perlrakulanguage-designtype-systemoperatorsprogramming-languages

Start With One Line

say 0.1 + 0.2 == 0.3;   # True

An expression that returns False in most languages returns True in Raku.

The reason is simple: Raku’s decimal literals are rationals (Rat) by default. 0.1 is not “the double closest to one tenth” — it is held as the fraction 1/10 itself. If you want floating point, you ask for Num.

There is a cost. Rational arithmetic is slower than floating point, and growing denominators need managing. They made it the default anyway, because not surprising you when you write the obvious thing was the higher priority.

That one line is a scale model of Perl 6’s entire design. Correctness bought with implementation cost. This instalment is the shopping list.

1. Sigil Invariance — the Famous Incompatibility

In Perl 5, a variable’s sigil changes with how you access it.

my @array = (1, 2, 3);
my $first = $array[0];      # $ , not @
my @slice = @array[0, 1];   # @ here

The rule is consistent: the sigil marks how many values come out. It is also the single biggest thing beginners trip over.

Perl 6 chose the opposite rule.

my @array = 1, 2, 3;
my $first = @array[0];      # stays @

“A sigil is the variable’s type, not its context.”

One rule change — and with it, Perl 5 code stops working at the syntax level. The decision to break compatibility is concentrated right here.

2. Twigils — One More Character After the Sigil

Perl 6 lets you put another character after the sigil, spelling out the kind of variable.

Twigil Meaning Example
! Class attribute (private) $!name
. Attribute via its accessor $.name
* Dynamic variable (looked up through callers) $*IN, $*CWD
? Compile-time constant $?FILE, $?LINE
^ Auto-declared positional parameter { $^a + $^b }

You can see the kind of scope just by looking. The clearest case is Perl 5’s local (dynamic scope) being tidied up into $*.

3. Putting OO Into the Language

As part 2 covered, Perl 5’s OO was assembled from existing parts. bless is a function; new is a convention.

Perl 6 has class / has / method / role as language features.

class Point {
    has $.x = 0;
    has $.y = 0;
    method to-string { "($!x, $!y)" }
}

The one to note is role. Introduced as an alternative to multiple inheritance, it descends from the Smalltalk Traits research: state and behaviour bundled into composable units, with collisions detected at compile time.

And this object model flowed back into Perl 5 before Perl 6 ever shipped. That is Moose. As mentioned in part 2, Moose was a port of Perl 6’s object model. Perl 6’s design was changing Perl 5 before it was itself released.

4. Making Types Writable (but Optional)

sub add(Int $a, Int $b --> Int) { $a + $b }

You may omit them; if you write them, they are checked at runtime. Gradual typing.

And subset gives you types with predicates.

subset Even of Int where * %% 2;
subset Positive of Numeric where * > 0;

sub half(Even $n) { $n div 2 }
half(4);   # fine
half(3);   # type constraint failure

Any predicate can go in the where clause. Validation lives in the type definition instead of at the top of every function.

This is the typing version of “easy things easy, hard things possible” from part 1.

5. Multiple Dispatch

One name, selected among by argument type, count, and constraint.

multi greet(Str $name)          { "Hello, $name" }
multi greet(Int $times)         { "Hi " x $times }
multi greet(Str $name, Int $n)  { "Hello, $name" x $n }
multi greet($x where * < 0)     { "Negative!" }

Where Java and C++ overloading resolves on static types, Raku resolves on the runtime types of the values. Closer to Common Lisp’s CLOS.

When several candidates match, the more specific one wins, and a candidate with a where constraint counts as more specific than one with only a type.

6. Treating Operators Meta-ly

Perl 6’s distinctive invention is the meta-operator: an operator that takes an operator and produces an operator.

Meta-operator Example Meaning
reduce [ ] [+] 1..10 Fold. 55
hyper »« @a »+» 1 Apply elementwise
cross X @a X @b Cartesian product
zip Z @a Z @b Pair up
negate ! !== Auto-generate the negated form
assign = min= Auto-generate the assigning form

Instead of adding N operators, add the rule that makes operators.

There is a lesson from Perl 5 in this. Perl 5 grew by adding operators. It is also the design-side answer to the bias described in part 3 — that RFCs only ever ask to add.

Here is this series’ fourth pattern.

Raise what gets added from features to rules. Features multiply as you add them; rules do not.

7. Junctions — Superposed Values

my $x = 2;
my @list = 1, 2, 3;

if $x == any(@list) { say 'equal to one of them' }   # runs
if $x >  all(@list) { say 'greater than all' }       # does not run

say    $x == any(@list);   # any(False, True, False) — the distributed result, as is
say so $x == any(@list);   # True — collapsed in boolean context

any / all / one / none are values, and comparisons distribute over them automatically. say shows the distributed result as it is; it collapses only in boolean context — if, or so. The feature is usually explained by analogy to quantum superposition.

It reaches into the type system too: junctions do not sit under Any. So passing one to a function that takes an ordinary type triggers the distribution. The type hierarchy has a reserved place for this feature.

8. Lazy Lists and Infinite Sequences

my @fib = 1, 1, * + * ... *;   # infinite Fibonacci
say @fib[^10];                  # (1 1 2 3 5 8 13 21 34 55)

... is the sequence operator; the trailing * (Whatever) means “forever.” Lists are lazy by default, so you can put an infinite sequence in a variable and take as much as you need later.

9. Changing the Grammar Itself

Raku’s parser is written as a Raku grammar. And users can extend the grammar at compile time.

sub infix:<∈>($x, @set) { $x (elem) @set }
say 2 ∈ (1, 2, 3);     # True

Operators are defined under names like infix:<...> / prefix:<...> / postfix:<...>, with associativity and precedence specified. The moment you define one, it is really syntax.

Grammars themselves get part 11. It is the thing Raku has that other languages don’t.

What You See When You Lay Them Out

Nine items above. Look at them again.

  • Rationals by default — designing the numeric tower
  • Sigil invariance — changing the entire look of the language
  • OO in the language — designing an object model and a MOP
  • Gradual typing — designing a type system
  • Multiple dispatch — designing a dispatch mechanism
  • Meta-operators — designing an operator system
  • Junctions — adding a new inhabitant to the type hierarchy
  • Lazy lists — changing the evaluation strategy
  • Grammar extension — opening the parser to users

Any single one of these is a language’s worth of work.

And they are not independent. Junctions touch the type hierarchy, laziness touches dispatch resolution, grammar extension touches everything. There is a decision to make for every combination.

At the end of part 3 I said there were four reasons it took fifteen years. This is the first.

The design was too ambitious.

This is not a story about sloth. It is a story about how much they set out to do. And they largely did it. It took fifteen years.

From next time, we move to the people who tried to make it actually run. Starting with the fact that for nearly five years, there was nothing that ran.


Next (part 5): Four and a half years without an implementation, and one year of Pugs. In February 2005, a Perl 6 written in Haskell appears. It did not merely run — it rewrote the relationship between specification and implementation.

← Back to The Lineage of Perl and Raku