Scalar
是一个内部间接引用,也就是说,一种间接引用值的方式,在大多数情况下,在普通使用 Raku 时是不可见的。它是与 $
符号关联的默认容器类型。可以通过用 $(…)
将值括起来,将一个文字 Scalar
放在任何文字周围。此符号将在 .raku
方法的输出中出现在某些重要的地方,以注意 Scalar
的存在。
当一个值被赋值给一个 $
符号变量时,该变量实际上将绑定到一个 Scalar
,而 Scalar
又将绑定到该值。当一个 Scalar
被赋值给一个 $
符号变量时,该 Scalar
绑定的值将绑定到该变量绑定的 Scalar
(如果需要,将创建一个新的 Scalar
)。
此外,Scalar
将所有方法调用委托给它们包含的值。因此,Scalar
在大多数情况下都是不可见的。然而,有一个重要的地方,Scalar
有明显的影响:一个 Scalar
容器会保护其内容不受大多数 Raku 核心列表操作的 扁平化。
say |(1,2,$(3,4)); # OUTPUT: «12(3 4)»
这些 Scalar
容器也可以通过赋值给一个 匿名标量变量 来动态创建
say |(1,2, $ = (3,4)); # OUTPUT: «12(3 4)»
一个 $
符号变量可以使用绑定运算符 :=
直接绑定到一个值,而无需中间 Scalar
。你可以通过检查内省伪方法 .VAR
的输出,来判断是否已完成此操作
my = 1;.^name.say; # OUTPUT: «Int».VAR.^name.say; # OUTPUT: «Scalar»my := 1;.^name.say; # OUTPUT: «Int».VAR.^name.say; # OUTPUT: «Int»
当值被赋值给 Array
的一个元素时,也会发生同样的事情,但是,List
直接包含它们的值
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»
数组元素也可以使用 :=
直接绑定到值;但是,不建议这样做,因为它可能会导致混淆。这样做会破坏 .raku
输出的精确往返——因为 Array
假定在每个元素周围放置 Scalar
,所以 Scalar
在 Array.raku
的输出中不使用 $
表示。
[1, $(2, 3)].raku.say; # OUTPUT: «[1, (2, 3)]»(1, $(2, 3)).raku.say; # OUTPUT: «(1, $(2, 3))»
将 Scalar
绑定到 $
符号变量会用给定的 Scalar
替换该变量中现有的 Scalar
(如果存在)。这意味着多个变量可以引用同一个 Scalar
。由于 Scalar
可以变异,因此可以通过只改变其中一个变量来改变两个变量的值
my = 1;my := ;= 2;.say; # OUTPUT: «2»
Raku 允许使用带有 静态单赋值 (SSA) 样式的常量,即使使用赋值 (=
),这些常量也会直接绑定到它们的值,而不会使用中间 Scalar
容器。可以通过将 $
符号变量赋值给它们来强制它们使用 Scalar
,此时,它们的行为完全像 $
符号变量。
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»
标量原子操作§
可以使用硬件支持的原子比较和交换操作来更改 Scalar
的值。这在实现无锁数据结构和算法时很有用。它还可以以“原子”方式获取和赋值,这确保了适当的内存屏障,并防止了内存访问的不必要的优化。
将与原子操作一起使用的 Scalar
应始终在对它执行任何原子操作之前使用值显式初始化。这是为了避免与延迟分配和自动生成竞争。例如
cas([5], , )
原则上会起作用,因为 Array
由 Scalar
容器组成。但是,只有在初始赋值时容器才会绑定到数组中。因此,将存在一个绑定竞争。Scalar
原子操作永远不会检查或执行任何此类自动生成,以便使此类错误更加明显(而不是仅在压力下观察到)。
内省§
方法§
method of(Scalar: --> Mu)
返回容器的类型约束。
示例
my Cool = 42;say .VAR.of; # OUTPUT: «(Cool)»
方法 default§
method default(Scalar: --> Str)
返回与容器关联的默认值。
示例
my is default(666) = 42;say .VAR.default; # OUTPUT: «666»
方法 name§
method name(Scalar: --> Str)
返回与容器关联的名称。
示例
my = 42;say .VAR.name; # OUTPUT: «$x»
方法 dynamic§
method dynamic(Scalar: --> Bool)
对于标量,它将返回 False
。
示例
my = 42;say .VAR.dynamic; # OUTPUT: «True»
请注意,您必须使用 VAR
方法才能获取该信息。
my is dynamic = [1, 2, 3];say .dynamic; # OUTPUT: «False» (wrong, don't do this)say .VAR.dynamic; # OUTPUT: «True» (correct approach)
例程§
子例程 atomic-assign§
multi atomic-assign( is rw, )
对 Scalar
$target
执行 $value
的原子赋值。atomic-assign
例程确保执行任何必需的屏障,以便将更改后的值“发布”到其他线程。
子例程 atomic-fetch§
multi atomic-fetch( is rw)
对 Scalar
$target
中的值执行原子读取并返回读取的值。使用此例程而不是简单地使用变量可确保看到来自其他线程的变量的最新更新,既通过执行任何必需的硬件屏障,又通过防止编译器提升读取。例如
my = False;startuntil atomic-fetch()
肯定会终止,而在
my = False;startuntil
编译器可以合法地观察到 $started
不会在循环中更新,因此将读取提升出循环,从而导致程序永远不会终止。
子例程 cas§
multi cas(Mu is rw, Mu \expected, Mu \value)multi cas(Mu is rw, )
对 Scalar
$target
中的值执行原子比较和交换。第一种形式具有如下语义
my = ;if <> =:= <>return ;
只是它作为一个单一的硬件支持的原子指令执行,就好像在执行期间阻止了对 $target
的所有内存访问一样。因此,可以从多个线程尝试操作,而无需任何其他同步。由于它是引用比较,因此此操作通常对值类型没有意义。
例如
constant NOT_STARTED = Any.new;constant STARTED = Any.new;my = NOT_STARTED;await startxx 4
将可靠地仅打印 Master!
一次,因为只有一个线程会成功地将 Scalar
从 NOT_STARTED
更改为 STARTED
。
第二个形式,采用代码对象,将首先对当前值进行原子获取,并用它调用代码对象。然后,它将尝试对目标进行原子比较和交换,使用传递给代码对象的值作为expected
,并使用代码对象的结果作为value
。如果此操作失败,它将读取最新值并重试,直到 CAS 操作成功。
因此,可以以无锁方式将一个项目添加到链表的头部,如下所示
my Node = Node;await startxx 4;
这将可靠地构建一个包含 4000 个项目的链表,其中 4 个节点的每个值范围从 0 到 999。
注意:在 Rakudo 2020.12 版本之前,$target
、expected
和 value
具有 Any
类型约束。
操作符§
中缀 ⚛=§
multi infix:<⚛=>( is rw, )
对 Scalar
$target
执行 $value
的原子赋值。⚛=
操作符确保执行任何必需的屏障,以便将更改后的值“发布”到其他线程。
前缀 ⚛§
multi prefix:<⚛>( is rw)
对 Scalar
$target
中的值执行原子读取并返回读取的值。使用此操作符而不是简单地使用变量可确保看到其他线程对变量的最新更新,既可以通过执行任何必需的硬件屏障,也可以防止编译器提升读取。例如
my = False;startuntil ⚛
肯定会终止,而在
my = False;startuntil
编译器可以合法地观察到 $started
不会在循环中更新,因此将读取提升出循环,从而导致程序永远不会终止。