class Scalar
A mostly transparent container used for indirections
1 | Atomic operations on Scalar |
2 | Introspection |
2.1 | method of |
2.2 | method default |
2.3 | method name |
2.4 | method dynamic |
3 | Routines |
3.1 | sub atomic-assign |
3.2 | sub atomic-fetch |
3.3 | sub cas |
4 | Operators |
4.1 | infix ⚛= |
4.2 | prefix ⚛ |
5 | Type Graph |
A Scalar
is an internal indirection 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 a literal value by enclosing the value in $(…)
. This notation will appear in the output of a .perl
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
will shield its content from flattening by most Raku core list operations.
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 .perl
output – since Array
s are assumed to place Scalar
s around each element, Scalar
s are not denoted with $
in the output of Array.perl
.
[1, $(2, 3)].perl.say; # OUTPUT: «[1, (2, 3)]»(1, $(2, 3)).perl.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»
SSA-style constants bind directly to their value with no intervening Scalar
, 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)
Returns whether the variable is visible in dynamic variable lookups.
Example:
my = 42;say .VAR.dynamic; # OUTPUT: «True»
Routines
sub atomic-assign
Defined as:
multi sub 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 sub 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
Defined as:
multi sub cas( is rw, , )multi sub cas( 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.
Operators
infix ⚛=
multi sub 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 sub 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.