存储代理获取

发布: (2026年2月20日 GMT+8 19:39)
11 分钟阅读
原文: Dev.to

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

注意: $_ 不是指向内存位置的指针或引用。它绑定的是容器对象,容器对象知道如何 FETCHSTORE

切片同样返回容器

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 类,展示了其 FETCHSTORE 命名参数,说明如何构建完全自定义的容器。

敬请期待下一集!

0 浏览
Back to Blog

相关文章

阅读更多 »