I’m not going to be original anyway and gonna start with the most traditional example around:

my $foo = “Hello world!”;
say $foo;

It shouldn’t be to hard to see that what’s done is a variable declared and then it is printed to the console (say is similar to print and always appends a new line at the end).

Let’s bring a little useless fun into this snippet. First, start with a couple of additional lines:

my $foo = “Hello world!”;
my $bar = $foo;
$bar ~= “ This is Raku!”
say $foo;

Keeping in mind that ~= is a string append operator, what output do we expect here? Apparently, it’s

Hello world!

again. So far, so good! But try adding a single colon:

my $foo = “Hello world!”;
my $bar := $foo;
$bar ~= “ This is Raku!”
say $foo;

and the output becomes:

Hello world! This is Raku!

:= is called binding operator and the rest of this chapter I’m going to deal with explaining of what’s going on in the above example.

Variables? Not Really.

Even though I used term variable above that wouldn’t be fully correct one to use. Normally a variable is considered to be a mutable entity. But more importantly, it is expected to be a name given to a value in memory. In Raku this is not really a case. Consider, for example:

my $foo := 42;
++$foo; # Error: attempt to modify immutable value

Not that other languages don’t support immutable variables, but the way things work in Raku makes it more appropriate to use another term: symbol. In other words, a Raku compiler deals internally with symbols bound to objects. The nature of a object defines how the symbol can be used in a particular context and wether it can be used at all.

What a symbol is from the point of view of Raku compiler? It is an entry in a symbol table. Or, in other words, the compiler just knows that such thing as $foo exists. At run time the code also knows what it is bound to. If we declare a constant:

constant FOO = “The Answer!”;

it would also be registered as a symbol. Or a subroutine:

sub bar { say $foo; }

which would get installed as a symbol &bar bound to a Sub object.

“Good”, you’d say, “but what makes the difference between $foo = 42 and $foo := 42?”

Containers

There is way in Raku to ask what object we get when requesting a symbol. For example:

say $foo.WHAT; # (Int)

would tell us that the value behind $foo has type Int. The braces around the Int in the output is a convention in Raku to denote a type object name.

Note: The previous article covers some aspects of object introspection in Raku.

Note that in the example above I didn’t specify how exactly $foo was initialized: with assign ( = ) operator or binding ( :=). This is because there is no visible difference between the two from the point of view of .WHAT. But we do know that the difference exists! After all, you asked me this question explicitly at the end of the previous section!

my $foo := 42;
my $bar = 42;
say $foo.VAR.WHAT; # (Int)
say $bar.VAR.WHAT; # (Scalar)

.VAR is what gives us the object our symbol is bound to. .WHAT tells us the type of it. And where binding results in evident Int type, assignment produces a Scalar which is something special in Raku. It is a container meaning that objects of this type are supposed to contain a value. Containers have special support by Raku. In particular, Scalar is so transparent that there is no other way of getting hold of it other than with .VAR. And even then some operations on it would end up applied to the value stored by it! Besides, there is really no legitimate Raku-only way to create a Scalar manually. The normal state of the things is when it is created for us by the compiler when necessary. In the example above, this is what happens when $bar is declared.

At this point I can give more clear definion of the difference between the assignment and binding operators when used outside of a symbol declaration:

  • the binding operator := is handled by the compiler directly by associating (binding) a symbol with an object
  • the assignment = tells the compiler to generate code which would take the value from operator’s right hand side and store it into the container on the left hand side

Note that in the latter case if the object bound is not capable of storing something our program will terminate with a error. Roughly, this is what happens when we try $foo := 42; ++$foo; because ++ acts similarly to = and tries storing 43 back to $foo.

At this point we can get back to the first example, where the binding operator is first seen:

my $bar := $foo;

Because binding is handled by the compiler directly, the code it produces doesn’t go deep but takes what $foo points at and binds it to $bar. In our case it happens to be a Scalar object. As a result of this binding we end up with $foo and $bar actually referencing the same things! Therefore, when we append a string, we alter the scalar container content and the modification is then available via either of the two symbols! Simply put, $foo and $bar became aliases for the same scalar.

To demonstrate the difference between a symbol and a container, let me use another example:

my $foo = “this is foo”;
my $bar = “this is bar”;
my $baz := $foo;
say $baz; # this is foo
$foo := $bar;
say $foo; # this is bar
say $baz; # this is foo

At the first glance it could be counter-intuitive for a beginner. We bind $baz to $foo, next we bind $foo to $bar – then why $baz did not change? Exactly for the reason that the compiler binds a symbol to a container (to an object in general), not to another symbol. Thus, $bar remains bound to the container with the string ”this is foo” while $foo is re-bound to the scalar containing ”this is bar”.

More Containers And Interactions

Scalar is not the only container type provided by Raku. Consider this code snippet:

my @a = [1, 2];
my %h = a => 1, b => 2;
say @a.VAR.WHAT; # (Array)
say %h.VAR.WHAT; # (Hash)

The Array and Hash are container types too. There is one specific container type named Proxy, but I better leave it for another article.

One important property of containers is that they may contain each other. For example:

my $a = [1, 2];
say $a.WHAT;      # (Array)
say $a.VAR.WHAT;  # (Scalar)
say $a.raku;      # $[1, 2]

Mind the $ sign before the opening square bracket in the last line (see raku method documentation). It signals that the array is containerized in a scalar. This is important to know because depending on context Raku might handle the array differently. A situation when this happens often comes as a surprise even to not so beginners:

my @a;
my $foo = [1, 2];
@a = $foo;
say @a.elems; # 1

Oops, we found a bug?! Of course not. Because when the assignment to @a finds a scalar on the right hand side it considers it as a single value:

say @a.raku; # [[1, 2],]

When array-to-array assignment is required it could be achieved in several ways (remember, TIMTOWTDI!). One is to use de-containerization operator:

@a = $a<>;

<> takes the value out of the scalar containing it and returns as the statement result making the line above similar to:

my @a;
my @b = [1, 2];
@a = @b;
say @a.raku; # [1, 2]

Other ways are not related to our subject here and I’m willing not to focus on them.

Why containers?

The concept of containers may seem confusing at first. But a deeper look into it reveals that it allows to solve many problems and provide features which otherwise would be hard to implement.

Let’s consider an example. Raku provides a concept of trait which is a part of a symbol declaration which somehow affects the object the symbol is bound to. Again, I won’t be going deeper into traits now, just provide an example of one of them, is default:

my $foo is default(42);
say $foo; # 42

Without the container concept this kind of behavior would require deep support from the compiler which would have to track all possible uses of $foo and handle the cases when a value is requested from uninitialized variable. Instead, the trait is applied to the container object. Now whenever the scalar is requested for a value it first consults with its internal state and returns the default value unless already once assigned to something non-Nil. Similarly, a couple of other traits operate via the functionality provided by Scalar.

Another interesting aspect of using containers is parameter passing. With a great deal of simplification, I could say that Raku always passes parameters by reference. This way positive performance impact is achieved when big objects are passed as function arguments; but it makes all function arguments immutable by default. In rare cases when we want to re-use an argument later is copy trait could be applied to it similarly as we’ve done with is default to $foo above. is copy wraps the argument object into a scalar for us and binds the argument symbol to the scalar. Voilá! We have a mutable thing! Similarly, is rw trait binds function parameter to argument’s container allowing modification of the original value on the caller side.

Where next?

Raku documentation project provides much more in-depth details.


I would be very thankful for any report about errors found on this page!

Comments