存储代理获取
Source: Dev.to
看起来您只提供了来源链接,而没有提供需要翻译的正文内容。请您把文章的文本粘贴在这里,我就可以帮您将其翻译成简体中文,并保持原有的格式、Markdown 语法和技术术语不变。
Cases of UPPER – Part 12
Raku 中的容器
这是 Cases of UPPER 系列的第 12 篇,记录了全部使用 UPPERCASE 编写的 Raku 语法元素。
本篇我们来看看 容器——在 Raku 中实际保存值的对象。
绑定 vs. 赋值
Raku 实际上只认识 绑定 (:=)。
赋值 (=) 只是一种语法糖,最终也会执行一次绑定。
什么情况?
大多数你在 Raku 中看到的标量变量都是对象,这些对象包含一个属性,当你给变量赋值时,值会绑定到该属性上。这类对象称为 标量容器(或简称 容器)。
简而言之:Raku 中的赋值就是对标量对象属性的绑定。
理解容器是想成为高效 Raku 开发者的必经之路——对我而言也是如此!
一个(非常)简化的模型
# Pseudocode – not real Raku
class Scalar {
has $!value;
method STORE($new-value) {
$!value := $new-value;
}
method FETCH() {
$!value;
}
}
像下面这样的赋值
my $a;
$a = 42;
可以想象成
my $a := Scalar.new; # 将 $a 绑定到一个新容器
$a.STORE(42); # 存入值
而打印变量(say $a)相当于
say $a.FETCH;
实际情况要复杂一些,因为:
- 类型约束 (
my Int $a) 可能会附加在变量上。 - 变量可以有 默认值 (
my $a is default(42))。
所有这些额外信息都存放在 容器描述符 中——这是实现细节,您不应直接依赖它,因为其接口可能会变化。
为什么要间接?
赋值是最频繁执行的操作之一,所以 Raku 为了性能对其做了许多快捷实现。
拥有容器的心智模型可以帮助您理解大量语言构造。
返回容器(rw 特性)
从块(或通过 return)返回的值默认会 去容器化:
my $a = 42;
sub foo() { $a } # 返回 *值* 42
foo() = 666; # ❌ 不能给不可变的 Int 赋值
如果需要 返回容器本身,有两种简便方式。
1. is rw 特性
my $a = 42;
sub foo() is rw { $a } # 返回容器
foo() = 666; # 有效
say $a; # 666
2. return-rw 语句
my $a = 42;
sub foo() { return-rw $a }
foo() = 666;
say $a; # 666
这些机制在给数组元素赋值时会被使用。
在核心实现中,后缀 […] 运算符以及底层的 AT-POS 方法都带有 is rw 特性以提升速度(return-rw 语句会带来一点点额外开销)。
数组和哈希中的容器
当用值初始化数组时,每个元素都会得到自己的容器。
您可以 取值、存值,或 绑定 一个容器到另一个变量。
my @a = 1, 2, 3, 4, 5;
$_++ for @a; # 为每个元素递增
say @a; # [2 3 4 5 6]
- 循环期间,主题变量
$_绑定 到每个元素的容器,所以$_++实际上是对该容器调用STORE。
注意:
$_不是指向内存位置的指针或引用。它绑定的是容器对象,容器对象知道如何FETCH和STORE。
切片同样返回容器
my @a = 1, 2, 3, 4, 5;
$_++ for @a[0, *-1]; # 只对首尾元素递增
say @a; # [2 2 3 4 6]
切片 [0, *-1] 产生两个容器(首元素和尾元素),只有这两个被修改。
相同的思路也适用于哈希:
my %h = a => 42, b => 666;
$_++ for %h.values; # 为每个值递增
say %h; # {a => 43, b => 667}
用户代码中的直接绑定
您可以显式地将一个标量绑定到另一个标量:
my $```
> **Source:** ...
```raku
a = 42;
my $b := $a; # $b is bound to $a’s container
$b = 666; # stores into the same container
say $a; # 666
将 $b 赋值实际上是向 $a 所持有的容器写入。
Assigning to non‑existent array elements
Raku 允许你给尚不存在的数组索引赋值:
my @a;
@a[3] = 42;
say @a; # [(Any) (Any) (Any) 42]
即使索引 3 起初没有容器,赋值操作也会即时创建一个。
秘密仍然在于 容器描述符。
让我们把伪代码细化一下,加入描述符:
# Pseudocode – not real Raku
class Scalar {
has $!descriptor; # holds name, type, defaults, etc.
has $!value;
method STORE($value) {
$!value := $!descriptor.process($value);
}
method FETCH() {
$!value;
}
}
现在 STORE 会委托给 $!descriptor.process($value),它可以:
- 记住变量的名称。
- 强制类型约束。
- 提供默认值。
- 执行容器所需的其他任何 bookkeeping(记录工作)。
Take‑away
- 容器 是 Raku 变量模型的核心。
- 赋值(
=)只是绑定(:=)的语法糖。 is rw特性和return‑rw让你返回容器本身而不是它的值。- 数组、哈希、切片,甚至循环主题都通过绑定到容器实现。
- 容器描述符负责类型检查、默认值以及其他元数据。
理解这些概念后,许多看似“魔法”的 Raku 特性就会迎刃而解,并且你会更有信心编写高效、惯用的代码。祝你玩得开心,尽情绑定容器吧!
Raku 中的容器和代理
实现 Rakudo 时有许多不同类型的容器描述符类,全部以 ContainerDescriptor:: 开头。它们都被视为实现细节。
数组和哈希描述符
当对数组中不存在的元素调用 AT‑POS 方法时,会创建一个具有特殊描述符的容器,它知道自己属于哪个 Array,以及应该存放在什么索引位置。
对 AT‑KEY(如第 10 部分所示)返回的容器的描述符也是如此;它知道在被赋值时应存入哪个 Hash,以及使用哪个键。
这种特殊行为让数组和哈希真正实现了 DWIM(智能推断)。
这些描述符还引入了一种远程动作特性,可能会让你喜欢,也可能不喜欢:
my @a;
my $b := @a[3];
say @a; # []
$b = 42;
say @a; # [(Any) (Any) (Any) 42]
注意: 绑定后数组中的元素并未被初始化,只有在给
$b赋值后才会初始化。此行为是特意实现的,用来防止意外的自动活化(auto‑vivification)。
防止意外的自动活化
my %h;
say %h:exists; # False
say %h; # {}
%h = 42;
say %h; # {a => {b => 42}}
即使 %h 被视为 Associative,:exists 测试也不会在 %h 中创建 Hash。只有在给它赋值后,%h 及其嵌套结构才真正“活”起来。
Source: …
Proxy 类
Raku 提供了一个完全可自定义的类,让你可以创建自己的容器逻辑:Proxy。了解容器是如何工作的,对使用这个类非常有帮助。
创建一个简单的 Proxy
你只需要提供:
- 一个用于 获取 值的方式(
FETCH),以及 - 一个用于 存储 值的方式(
STORE)。
这些方法作为命名参数传递给 Proxy.new。常见的做法是把创建过程包装在一个子例程里,以便使用:
sub answer is rw {
my $value = 42;
Proxy.new(
FETCH => method () {
$value
},
STORE => method ($new) {
say "storing $new";
$value = $new;
}
)
}
my $a := answer; # 绑定,而不是赋值
say $a; # 42
$a = 666; # storing 666
say $a; # 666
- 子例程上的
is rw特性是必需的;否则返回的Proxy会在返回时被去容器化。 - 调用
answer的结果必须 绑定(:=)而不是 赋值;赋值同样会导致去容器化,失去其意义。
因为提供的方法闭包捕获了词法变量 $value,该变量会一直存活到 Proxy 对象被销毁,为代理提供了一种简便的值存储方式。
FETCH/STORE 内的灵活性
在提供的方法内部,你可以自由执行任何代码。例如,Hash::MutableKeys 发行版就利用了这一能力(另一种 BDD —— 博客驱动开发)。
如果你需要为 Proxy 对象提供描述符,可以自行创建;默认的 Proxy 并不包含描述符,因为它旨在提供最大的灵活性。
剧集概要
这是 “Cases of UPPER Language Elements in the Raku Programming Language” 的第十二集,也是讨论接口方法的第五集。
- Containers 已被描述,包括数组和哈希的特殊描述符。
- 引入了
Proxy类,展示了其FETCH和STORE命名参数,说明如何构建完全自定义的容器。
敬请期待下一集!