class Scalar
class Scalar {}
A Scalar
is an internal indirection, that is, a way to refer indirectly to a
value, which is, for most purposes, invisible during ordinary use of Raku. It is
the default container type associated with the $
sigil. A literal Scalar
may be placed around any literal by enclosing the value in $(ā¦)
. This
notation will appear in the output of a .raku
method in certain places where
it is important to note the presence of Scalar
s.
When a value is assigned to a $
-sigiled variable, the variable will
actually bind to a Scalar
, which in turn will bind to the value.
When a Scalar
is assigned to a $
-sigiled variable, the value
bound to by that Scalar
will be bound to the Scalar
which that
variable was bound to (a new one will be created if necessary.)
In addition, Scalar
s delegate all method calls to the value which they
contain. As such, Scalar
s are for the most part invisible. There is, however,
one important place where Scalar
s have a visible impact: a Scalar
container will shield its content from
flattening by most Raku core list
operations.
say |(1,2,$(3,4)); # OUTPUT: Ā«12(3 4)ā¤Ā»
These Scalar
containers can also be created on the fly by assigning to an
anonymous scalar variable:
say |(1,2, $ = (3,4)); # OUTPUT: Ā«12(3 4)ā¤Ā»
A $
-sigiled variable may be bound directly to a value with no
intermediate Scalar
using the binding operator :=
. You can
tell if this has been done by examining the output of the
introspective pseudo-method .VAR
:
my $a = 1;
$a.^name.say; # OUTPUT: Ā«Intā¤Ā»
$a.VAR.^name.say; # OUTPUT: Ā«Scalarā¤Ā»
my $b := 1;
$b.^name.say; # OUTPUT: Ā«Intā¤Ā»
$b.VAR.^name.say; # OUTPUT: Ā«Intā¤Ā»
This same thing happens when values are assigned to an element of an Array, however, Lists directly contain their values:
my @a = 1, 2, 3;
@a[0].^name.say; # OUTPUT: Ā«Intā¤Ā»
@a[0].VAR.^name.say; # OUTPUT: Ā«Scalarā¤Ā»
[1, 2, 3][0].^name.say; # OUTPUT: Ā«Intā¤Ā»
[1, 2, 3][0].VAR.^name.say; # OUTPUT: Ā«Scalarā¤Ā»
(1, 2, 3)[0].^name.say; # OUTPUT: Ā«Intā¤Ā»
(1, 2, 3)[0].VAR.^name.say; # OUTPUT: Ā«Intā¤Ā»
Array elements may be bound directly to values using :=
as well; however,
this is discouraged as it may lead to confusion. Doing so will break exact
round-tripping of .raku
output ā since Arrays are assumed to place
Scalar
s around each element, Scalar
s are not denoted with $
in the
output of Array.raku
.
[1, $(2, 3)].raku.say; # OUTPUT: Ā«[1, (2, 3)]ā¤Ā»
(1, $(2, 3)).raku.say; # OUTPUT: Ā«(1, $(2, 3))ā¤Ā»
Binding a Scalar
to a $
-sigiled variable replaces the existing
Scalar
in that variable, if any, with the given Scalar
.
That means more than one variable may refer to the same Scalar
.
Because the Scalar
may be mutated, this makes it possible to
alter the value of both variables by altering only one of them:
my $a = 1;
my $b := $a;
$b = 2;
$a.say; # OUTPUT: Ā«2ā¤Ā»
Raku allows the use of constants with a
static single assignment (SSA)
style which bind directly to their value with no intervening Scalar
container, even when assignment (=
) is used. They may be forced to use a
Scalar
by assigning a $
-sigiled variable to them, at which point, they
behave entirely like $
-sigiled variables.
my \c = 1;
c.^name.say; # OUTPUT: Ā«Intā¤Ā»
c.VAR.^name.say; # OUTPUT: Ā«Intā¤Ā»
my $a = 1;
my \d = $a; # just "my \d = $ = 1" works, too
d.^name.say; # OUTPUT: Ā«Intā¤Ā»
d.VAR.^name.say; # OUTPUT: Ā«Scalarā¤Ā»
d = 2; # ok
c = 2; # fails
CATCH { default { put .^name, ': ', .Str } };
# OUTPUT: Ā«X::Assignment::RO: Cannot modify an immutable Intā¤Ā»
Atomic operations on Scalar
A Scalar
can have its value changed using a hardware-supported atomic
compare and swap operation. This is useful when implementing lock free data
structures and algorithms. It may also be fetched and assigned to in an
"atomic" fashion, which ensures appropriate memory barriering and prevents
unwanted optimizations of memory accesses.
A Scalar
that will be used with an atomic operation should always be
explicitly initialized with a value before any atomic operations are
performed upon it. This is to avoid races with lazy allocation and
auto-vivification. For example:
cas(@a[5], $expected, $value)
Will work in principle since an Array consists of Scalar
containers.
However, the container is only bound into the array upon initial assignment.
Therefore, there would be a race to do that binding. The Scalar
atomic
operations will never check for or do any such auto-vivification, so as to
make such bugs much more evident (rather than only observed under stress).
Introspection
method of
method of(Scalar:D: --> Mu)
Returns the type constraint of the container.
Example:
my Cool $x = 42;
say $x.VAR.of; # OUTPUT: Ā«(Cool)ā¤Ā»
method default
method default(Scalar:D: --> Str)
Returns the default value associated with the container.
Example:
my $x is default(666) = 42;
say $x.VAR.default; # OUTPUT: Ā«666ā¤Ā»
method name
method name(Scalar:D: --> Str)
Returns the name associated with the container.
Example:
my $x = 42;
say $x.VAR.name; # OUTPUT: Ā«$xā¤Ā»
method dynamic
method dynamic(Scalar:D: --> Bool)
It will return False
for scalars.
Example:
my $*FOO = 42;
say $*FOO.VAR.dynamic; # OUTPUT: Ā«Trueā¤Ā»
Note that you have to use the VAR
method in order to get that information.
my $s is dynamic = [1, 2, 3];
say $s.dynamic; # OUTPUT: Ā«Falseā¤Ā» (wrong, don't do this)
say $s.VAR.dynamic; # OUTPUT: Ā«Trueā¤Ā» (correct approach)
Routines
sub atomic-assign
multi atomic-assign($target is rw, $value)
Performs an atomic assignment of $value
into the Scalar
$target
. The
atomic-assign
routine ensures that any required barriers are performed such
that the changed value will be "published" to other threads.
sub atomic-fetch
multi atomic-fetch($target is rw)
Performs an atomic read of the value in the Scalar
$target
and returns
the read value. Using this routine instead of simply using the variable
ensures that the latest update to the variable from other threads will be seen,
both by doing any required hardware barriers and also preventing the compiler
from lifting reads. For example:
my $started = False;
start { atomic-assign($started, True) }
until atomic-fetch($started) { }
Is certain to terminate, while in:
my $started = False;
start { atomic-assign($started, True) }
until $started { }
It would be legal for a compiler to observe that $started
is not updated in
the loop, and so lift the read out of the loop, thus causing the program to
never terminate.
sub cas
multi cas(Mu $target is rw, Mu \expected, Mu \value)
multi cas(Mu $target is rw, &operation)
Performs an atomic compare and swap of the value in the Scalar
$target
.
The first form has semantics like:
my $seen = $target;
if $seen<> =:= $expected<> {
$target = $value;
}
return $seen;
Except it is performed as a single hardware-supported atomic instruction, as
if all memory access to $target
were blocked while it took place. Therefore
it is safe to attempt the operation from multiple threads without any other
synchronization. Since it is a reference comparison, this operation is usually
not sensible on value types.
For example:
constant NOT_STARTED = Any.new;
constant STARTED = Any.new;
my $master = NOT_STARTED;
await start {
if cas($master, NOT_STARTED, STARTED) === NOT_STARTED {
say "Master!"
}
} xx 4
Will reliably only ever print Master!
one time, as only one of the threads
will be successful in changing the Scalar
from NOT_STARTED
to
STARTED
.
The second form, taking a code object, will first do an atomic fetch of the
current value and invoke the code object with it. It will then try to do an
atomic compare and swap of the target, using the value passed to the code
object as expected
and the result of the code object as value
. If
this fails, it will read the latest value, and retry, until a CAS operation
succeeds.
Therefore, an item could be added to the head of a linked list in a lock free manner as follows:
class Node {
has $.value;
has Node $.next;
}
my Node $head = Node;
await start {
for ^1000 -> $value {
cas $head, -> $next { Node.new(:$value, :$next) }
}
} xx 4;
This will reliably build up a linked list of 4000 items, with 4 nodes with each value ranging from 0 up to 999.
Note: Before Rakudo version 2020.12, $target
, expected
and
value
had an Any type constraint.
Operators
infix ā=
multi infix:<ā=>($target is rw, $value)
Performs an atomic assignment of $value
into the Scalar
$target
. The
ā=
operator ensures that any required barriers are performed such that the
changed value will be "published" to other threads.
prefix ā
multi prefix:<ā>($target is rw)
Performs an atomic read of the value in the Scalar
$target
and returns
the read value. Using this operator instead of simply using the variable
ensures that the latest update to the variable from other threads will be seen,
both by doing any required hardware barriers and also preventing the compiler
from lifting reads. For example:
my $started = False;
start { $started ā= True }
until ā$started { }
Is certain to terminate, while in:
my $started = False;
start { $started ā= True }
until $started { }
It would be legal for a compiler to observe that $started
is not updated in
the loop, and so lift the read out of the loop, thus causing the program to
never terminate.