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 = 1;.^name.say; # OUTPUT: «Int».VAR.^name.say; # OUTPUT: «Scalar»my := 1;.^name.say; # OUTPUT: «Int».VAR.^name.say; # OUTPUT: «Int»
This same thing happens when values are assigned to an element of an Array
, however, List
s directly contain their values:
my = 1, 2, 3;[0].^name.say; # OUTPUT: «Int»[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 Array
s 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 = 1;my := ;= 2;.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 = 1;my \d = ; # just "my \d = $ = 1" works, tood.^name.say; # OUTPUT: «Int»d.VAR.^name.say; # OUTPUT: «Scalar»d = 2; # okc = 2; # failsCATCH ;# 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([5], , )
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: --> Mu)
Returns the type constraint of the container.
Example:
my Cool = 42;say .VAR.of; # OUTPUT: «(Cool)»
method default§
method default(Scalar: --> Str)
Returns the default value associated with the container.
Example:
my is default(666) = 42;say .VAR.default; # OUTPUT: «666»
method name§
method name(Scalar: --> Str)
Returns the name associated with the container.
Example:
my = 42;say .VAR.name; # OUTPUT: «$x»
method dynamic§
method dynamic(Scalar: --> Bool)
It will return False
for scalars.
Example:
my = 42;say .VAR.dynamic; # OUTPUT: «True»
Note that you have to use the VAR
method in order to get that information.
my is dynamic = [1, 2, 3];say .dynamic; # OUTPUT: «False» (wrong, don't do this)say .VAR.dynamic; # OUTPUT: «True» (correct approach)
Routines§
sub atomic-assign§
multi atomic-assign( is rw, )
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( 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 = False;startuntil atomic-fetch()
Is certain to terminate, while in:
my = False;startuntil
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 is rw, Mu \expected, Mu \value)multi cas(Mu is rw, )
Performs an atomic compare and swap of the value in the Scalar
$target
. The first form has semantics like:
my = ;if <> =:= <>return ;
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 = NOT_STARTED;await startxx 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:
my Node = Node;await startxx 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:<⚛=>( is rw, )
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:<⚛>( 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 = False;startuntil ⚛
Is certain to terminate, while in:
my = False;startuntil
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.