Containers

A low-level explanation of Raku containers

This section explains how raw data, variables and containers relate to each other in Raku. The different types of containers used in Raku are explained and the actions applicable to them like assigning, binding and flattening. More advanced topics like self-referential data, type constraints and custom containers are discussed at the end.

For a deeper discussion of the various kinds of ordered containers in Raku, see the overview of lists, sequences, and arrays; for unordered containers, see sets, bags, and mixes.

What is a variable?

Some people like to say "everything is an object", but in fact a variable is not a user-exposed object in Raku.

When the compiler encounters a variable scope declaration like my $x, it registers it in some internal symbol table. This internal symbol table is used to detect undeclared variables and to tie the code generation for the variable to the correct scope.

At runtime, a variable appears as an entry in a lexical pad, or lexpad for short. This is a per-scope data structure that stores a pointer for each variable.

In the case of my $x, the lexpad entry for the variable $x is a pointer to an object of type Scalar, usually just called the container.

Scalar containers

Although objects of type Scalar are everywhere in Raku, you rarely see them directly as objects, because most operations decontainerize, which means they act on the Scalar container's contents instead of the container itself.

In code like

my $x = 42;
    say $x;

the assignment $x = 42 stores a pointer to the Int object 42 in the scalar container to which the lexpad entry for $x points.

The assignment operator asks the container on the left to store the value on its right. What exactly that means is up to the container type. For Scalar it means "replace the previously stored value with the new one".

Note that subroutine signatures allow passing containers around:

sub f($a is rw) {
        $a = 23;
    }
    my $x = 42;
    f($x);
    say $x;         # OUTPUT: Ā«23ā¤Ā»

Inside the subroutine, the lexpad entry for $a points to the same container that $x points to outside the subroutine. Which is why assignment to $a also modifies the contents of $x.

Likewise, a routine can return a container if it is marked as is rw:

my $x = 23;
    sub f() is rw { $x };
    f() = 42;
    say $x;         # OUTPUT: Ā«42ā¤Ā»

For explicit returns, return-rw instead of return must be used.

Returning a container is how is rw attribute accessors work. So

class A {
        has $.attr is rw;
    }

is equivalent to

class A {
        has $!attr;
        method attr() is rw { $!attr }
    }

Scalar containers are transparent to type checks and most kinds of read-only accesses. A .VAR makes them visible:

my $x = 42;
    say $x.^name;       # OUTPUT: Ā«Intā¤Ā»
    say $x.VAR.^name;   # OUTPUT: Ā«Scalarā¤Ā»

And is rw on a parameter requires the presence of a writable Scalar container:

sub f($x is rw) { say $x };
    f 42;
    CATCH { default { say .^name, ': ', .Str } };
    # OUTPUT: Ā«X::Parameter::RW: Parameter '$x' expected a writable container, but got Int valueā¤Ā»

Callable containers

Callable containers provide a bridge between the syntax of a Routine call and the actual call of the method CALL-ME of the object that is stored in the container. The sigil & is required when declaring the container and has to be omitted when executing the Callable. The default type constraint is Callable.

my &callable = -> $Ī½ { say "$Ī½ is ", $Ī½ ~~ Int ?? "whole" !! "not whole" }
    callable(ā…“);   # OUTPUT: Ā«0.333333 is not wholeā¤Ā»
    callable(3);   # OUTPUT: Ā«3 is wholeā¤Ā»

The sigil has to be provided when referring to the value stored in the container. This in turn allows Routines to be used as arguments to calls.

sub f() {}
    my &g = sub {}
    sub caller(&c1, &c2){ c1, c2 }
    caller(&f, &g);

Binding

Next to assignment, Raku also supports binding with the := operator. When binding a value or a container to a variable, the lexpad entry of the variable is modified (and not just the container it points to). If you write

my $x := 42;

then the lexpad entry for $x directly points to the Int 42. Which means that you cannot assign to it anymore:

my $x := 42;
    $x = 23;
    CATCH { default { say .^name, ': ', .Str } };
    # OUTPUT: Ā«X::AdHoc: Cannot assign to an immutable valueā¤Ā»

You can also bind variables to other variables:

my $a = 0;
    my $b = 0;
    $a := $b;
    $b = 42;
    say $a;         # OUTPUT: Ā«42ā¤Ā»

Here, after the initial binding, the lexpad entries for $a and $b both point to the same scalar container, so assigning to one variable also changes the contents of the other.

You've seen this situation before: it is exactly what happened with the signature parameter marked as is rw.

Sigilless variables and parameters with the trait is raw always bind (whether = or := is used):

my $a = 42;
    my \b = $a;
    b++;
    say $a;         # OUTPUT: Ā«43ā¤Ā»
sub f($c is raw) { $c++ }
    f($a);
    say $a;         # OUTPUT: Ā«44ā¤Ā»

Scalar containers and listy things

There are a number of positional container types with slightly different semantics in Raku. The most basic one is List, which is created by the comma operator.

say (1, 2, 3).^name;    # OUTPUT: Ā«Listā¤Ā»

A list is immutable, which means you cannot change the number of elements in a list. But if one of the elements happens to be a scalar container, you can still assign to it:

my $x = 42;
    ($x, 1, 2)[0] = 23;
    say $x;                 # OUTPUT: Ā«23ā¤Ā»
    ($x, 1, 2)[1] = 23;     # Cannot modify an immutable value
    CATCH { default { say .^name, ': ', .Str } };
    # OUTPUT: Ā«X::Assignment::RO: Cannot modify an immutable Intā¤Ā»

So the list doesn't care about whether its elements are values or containers, they just store and retrieve whatever was given to them.

Lists can also be lazy; in that case, elements at the end are generated on demand from an iterator.

An Array is just like a list, except that it forces all its elements to be containers, which means that you can always assign to elements:

my @a = 1, 2, 3;
    @a[0] = 42;
    say @a;         # OUTPUT: Ā«[42 2 3]ā¤Ā»

@a actually stores three scalar containers. @a[0] returns one of them, and the assignment operator replaces the integer value stored in that container with the new one, 42.

Assigning and binding to array variables

Assignment to a scalar variable and to an array variable both do the same thing: discard the old value(s), and enter some new value(s).

Nevertheless, it's easy to observe how different they are:

my $x = 42; say $x.^name;   # OUTPUT: Ā«Intā¤Ā»
    my @a = 42; say @a.^name;   # OUTPUT: Ā«Arrayā¤Ā»

This is because the Scalar container type hides itself well, but Array makes no such effort. Also assignment to an array variable is coercive, so you can assign a non-array value to an array variable.

To place a non-Array into an array variable, binding works:

my @a := (1, 2, 3);
    say @a.^name;               # OUTPUT: Ā«Listā¤Ā»

Binding to array elements

As a curious side note, Raku supports binding to array elements:

my @a = (1, 2, 3);
    @a[0] := my $x;
    $x = 42;
    say @a;                     # OUTPUT: Ā«[42 2 3]ā¤Ā»

If you've read and understood the previous explanations, it is now time to wonder how this can possibly work. After all, binding to a variable requires a lexpad entry for that variable, and while there is one for an array, there aren't lexpad entries for each array element, because you cannot expand the lexpad at runtime.

The answer is that binding to array elements is recognized at the syntax level and instead of emitting code for a normal binding operation, a special method (called BIND-KEY) is called on the array. This method handles binding to array elements.

Note that, while supported, one should generally avoid directly binding uncontainerized things into array elements. Doing so may produce counter-intuitive results when the array is used later.

my @a = (1, 2, 3);
    @a[0] := 42;         # This is not recommended, use assignment instead.
    my $b := 42;
    @a[1] := $b;         # Nor is this.
    @a[2] = $b;          # ...but this is fine.
    @a[1, 2] := 1, 2;    # runtime error: X::Bind::Slice
    CATCH { default { say .^name, ': ', .Str } };
    # OUTPUT: Ā«X::Bind::Slice: Cannot bind to Array sliceā¤Ā»

Operations that mix Lists and Arrays generally protect against such a thing happening accidentally.

Flattening, items and containers

The % and @ sigils in Raku generally indicate multiple values to an iteration construct, whereas the $ sigil indicates only one value.

my @a = 1, 2, 3;
    for @a { };         # 3 iterations
    my $a = (1, 2, 3);
    for $a { };         # 1 iteration

@-sigiled variables do not flatten in list context:

my @a = 1, 2, 3;
    my @b = @a, 4, 5;
    say @b.elems;               # OUTPUT: Ā«3ā¤Ā»

There are operations that flatten out sublists that are not inside a scalar container: slurpy parameters (*@a) and explicit calls to flat:

my @a = 1, 2, 3;
    say (flat @a, 4, 5).elems;  # OUTPUT: Ā«5ā¤Ā»
sub f(*@x) { @x.elems };
    say f @a, 4, 5;             # OUTPUT: Ā«5ā¤Ā»

You can also use | to create a Slip, introducing a list into the other.

my @l := 1, 2, (3, 4, (5, 6)), [7, 8, (9, 10)];
    say (|@l, 11, 12);    # OUTPUT: Ā«(1 2 (3 4 (5 6)) [7 8 (9 10)] 11 12)ā¤Ā»
    say (flat @l, 11, 12) # OUTPUT: Ā«(1 2 3 4 5 6 7 8 (9 10) 11 12)ā¤Ā»

In the first case, every element of @l is slipped as the corresponding elements of the resulting list. flat, in the other hand, flattens all elements including the elements of the included array, except for (9 10).

As hinted above, scalar containers prevent that flattening:

sub f(*@x) { @x.elems };
    my @a = 1, 2, 3;
    say f $@a, 4, 5;            # OUTPUT: Ā«3ā¤Ā»

The @ character can also be used as a prefix to coerce the argument to a list, thus removing a scalar container:

my $x = (1, 2, 3);
    .say for @$x;               # 3 iterations

However, the decont operator <> is more appropriate to decontainerize items that aren't lists:

my $x = ^Inf .grep: *.is-prime;
    say "$_ is prime" for @$x;  # WRONG! List keeps values, thus leaking memory
    say "$_ is prime" for $x<>; # RIGHT. Simply decontainerize the Seq
my $y := ^Inf .grep: *.is-prime; # Even better; no Scalars involved at all

Methods generally don't care whether their invocant is in a scalar, so

my $x = (1, 2, 3);
    $x.map(*.say);              # 3 iterations

maps over a list of three elements, not of one.

Self-referential data

Container types, including Array and Hash, allow you to create self-referential structures.

my @a;
    @a[0] = @a;
    put @a.raku;
    # OUTPUT: Ā«((my @Array_75093712) = [@Array_75093712,])ā¤Ā»

Although Raku does not prevent you from creating and using self-referential data, by doing so you may end up in a loop trying to dump the data. As a last resort, you can use Promises to handle timeouts.

Type constraints

Any container can have a type constraint in the form of a type object or a subset. Both can be placed between a declarator and the variable name or after the trait of. The constraint is a property of the variable, not the container.

subset Three-letter of Str where .chars == 3;
    my Three-letter $acronym = "ƞFL";

In this case, the type constraint is the (compile-type defined) subset Three-letter.

The default type constraint of a Scalar container is Mu. Introspection of type constraints on containers is provided by .VAR.of method, which for @ and % sigiled variables gives the constraint for values:

my Str $x;
    say $x.VAR.of;  # OUTPUT: Ā«(Str)ā¤Ā»
    my Num @a;
    say @a.VAR.of;  # OUTPUT: Ā«(Num)ā¤Ā»
    my Int %h;
    say %h.VAR.of;  # OUTPUT: Ā«(Int)ā¤Ā»

Definedness constraints

A container can also enforce a variable to be defined. Put a smiley in the declaration:

my Int:D $def = 3;
    say $def;   # OUTPUT: Ā«3ā¤Ā»
    $def = Int; # Typecheck failure

You'll also need to initialize the variable in the declaration, it can't be left undefined after all.

It's also possible to have this constraint enforced in all variables declared in a scope with the default defined variables pragma. People coming from other languages where variables are always defined will want to have a look.

Custom containers

To provide custom containers Raku employs the class Proxy. Its constructor takes two arguments, FETCH AND STORE, that point to methods that are called when values are fetched or stored from the container. Type checks are not done by the container itself and other restrictions like readonlyness can be broken. The returned value must therefore be of the same type as the type of the variable it is bound to. We can use type captures to work with types in Raku.

sub lucky(::T $type) {
        my T $c-value; # closure variable
        return-rw Proxy.new(
            FETCH => method () { $c-value },
            STORE => method (T $new-value) {
                X::OutOfRange.new(what => 'number', got => '13', range => '-āˆž..12, 14..āˆž').throw
                    if $new-value == 13;
                $c-value = $new-value;
            }
        );
    }
my Int $a := lucky(Int);
    say $a = 12;    # OUTPUT: Ā«12ā¤Ā»
    say $a = 'FOO'; # X::TypeCheck::Binding
    say $a = 13;    # X::OutOfRange
    CATCH { default { say .^name, ': ', .Str } };

See Also

Contexts and contextualizers

What are contexts and how to switch into them

Control flow

Statements used to control the flow of execution

Enumeration

An example using the enum type

Exceptions

Using exceptions in Raku

Functions

Functions and functional programming in Raku

Grammars

Parsing and interpreting text

Hashes and maps

Working with associative arrays/dictionaries/hashes

Input/Output the definitive guide

Correctly use Raku IO

Lists, sequences, and arrays

Positional data constructs

Metaobject protocol (MOP)

Introspection and the Raku object system

Native calling interface

Call into dynamic libraries that follow the C calling convention

Raku native types

Using the types the compiler and hardware make available to you

Newline handling in Raku

How the different newline characters are handled, and how to change the behavior

Numerics

Numeric types available in Raku

Object orientation

Object orientation in Raku

Operators

Common Raku infixes, prefixes, postfixes, and more!

Packages

Organizing and referencing namespaced program elements

Performance

Measuring and improving runtime or compile-time performance

Phasers

Program execution phases and corresponding phaser blocks

Pragmas

Special modules that define certain aspects of the behavior of the code

Quoting constructs

Writing strings and word lists, in Raku

Regexes

Pattern matching against strings

Sets, bags, and mixes

Unordered collections of unique and weighted objects in Raku

Signature literals

A guide to signatures in Raku

Statement prefixes

Prefixes that alter the behavior of a statement or a set of them

Data structures

How Raku deals with data structures and what we can expect from them

Subscripts

Accessing data structure elements by index or key

Syntax

General rules of Raku syntax

System interaction

Working with the underlying operating system and running applications

Date and time functions

Processing date and time in Raku

Traits

Compile-time specification of behavior made easy

Unicode versus ASCII symbols

Unicode symbols and their ASCII equivalents

Unicode

Unicode support in Raku

Variables

Variables in Raku

Independent routines

Routines not defined within any class or role.

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.