通常需要从集合或数据结构中引用特定元素(或元素切片)。借用数学符号,其中向量 v 的分量将被称为 v₁, v₂, v₃,这个概念在 Raku 中被称为“下标”(或“索引”)。

基础§

Raku 提供了两种通用的下标接口

元素可以通过以下方式识别接口名称支持
[ ]从零开始的索引位置数组、列表、缓冲区、匹配等
{ }字符串或对象键关联哈希、包、混合、匹配等

Positional 下标§

Positional 下标(通过 postcircumfix [ ])通过元素的位置来访问有序集合中的元素。索引 0 指的是第一个元素,索引 1 指的是第二个元素,以此类推。

my @chores = "buy groceries""feed dog""wash car";
say @chores[0];  # OUTPUT: «buy groceries␤» 
say @chores[1];  # OUTPUT: «feed dog␤» 
say @chores[2];  # OUTPUT: «wash car␤»

请注意,Rakudo 的下标实现仅在 64 位构建中允许 63 位。请参阅 Stack Overflow。如果需要更大的下标,可以使用 Array::Sparse,或者使用带有 Associative 下标的 Hash(见下文)。

Associative 下标§

Associative 下标(通过 postcircumfix { })不需要集合按特定顺序保存其元素 - 相反,它使用唯一的键来访问每个值。键的性质取决于集合本身:例如,标准 Hash 使用字符串键,而 Mix 允许使用任意对象作为键,等等。

my %grade = Zoe => "C"Ben => "B+";
say %grade{"Zoe"};  # OUTPUT: «C␤» 
say %grade{"Ben"};  # OUTPUT: «B+␤» 
 
my $stats = ( Date.today => 4.18Date.new(2015,  4,  5=> 17.253 ).Mix;
say $stats{ Date.new(201544+ 1 };  # OUTPUT: «17.253␤»

要将单个单词字符串键传递给 { },您也可以使用 尖括号括起来的单词引用结构,就像它们是后缀运算符一样。

my %grade = Zoe => "C"Ben => "B+";
say %grade<Zoe>;    # OUTPUT: «C␤» 
say %grade<Ben>;    # OUTPUT: «B+␤»

这实际上只是语法糖,在编译时会转换为相应的 { } 形式。

%hash<foo bar>;       # same as %hash{ <foo bar> } 
%hash«foo "$var"»;    # same as %hash{ «foo "$var"» } 
%hash<<foo "$var">>;  # same as %hash{ <<foo "$var">> } 

您可能已经注意到,我们通过使用 => 运算符避免了对 Zoe 进行引用,但相同的运算符并没有对 Date.new(2015, 4, 5) 进行隐式引用,并且我们能够使用 $stats{ Date.new(2015, 4, 4) + 1 } 找到相同的元素。这是因为 => 仅对单个单词进行隐式引用,而“单词”是指标识符/名称。=> 运算符是为了防止我们意外地调用函数或使用具有该名称的常量。

哈希下标与 => 的作用不同。默认的 Hash 被设计成符合新用户从使用其他语言中获得的预期行为,并为了方便使用。在默认的 Hash 中,下标会将键强制转换为字符串,只要这些键产生 Cool 的东西。您可以对集合使用 .raku 来确定键是字符串还是对象。

1  => 1 ).raku.say;            # OUTPUT: «1 => 1␤» 
my %h%h{1}   = 1say %h.raku# OUTPUT: «{ "1" => 1 }␤» 
1/2 => 1 ).raku.say;           # OUTPUT: «0.5 => 1␤» 
my %h%h{1/2} = 1say %h.raku# OUTPUT: «{ "0.5" => 1 }␤» 
pi => 1 ).raku.say;            # OUTPUT: «:pi(1)␤» 
my %h%h{pi}  = 1say %h.raku# OUTPUT: «{ "3.14159265358979" => 1 }␤»

虽然对单个名称进行隐式引用是内置在 => 中的,但字符串转换并没有内置在花括号中:它是默认 Hash 的行为。并非所有类型的哈希或集合都这样做。

my %h := MixHash.new;
%h{pi} = 1%h.raku.say;         # OUTPUT: «(3.14159265358979e0=>1).MixHash␤» 

(任何 => 会转换为字符串的名称也可以用于使用“副词表示法”构建对,并且在通过 .raku 查看时会以这种方式出现,这就是为什么我们在上面看到 :pi(1) 的原因。)

应用下标§

下标可以应用于返回可下标对象的任何表达式,而不仅仅是变量。

say "__Hello__".match(/__(.*)__/)[0];   # OUTPUT: «「Hello」␤» 
say "__Hello__".match(/__(.*)__/).[0];  # same, in method notation

位置下标和关联下标并不相互排斥 - 例如,Match 对象支持两者(每个访问不同的数据集)。此外,为了使列表处理更加方便,类 Any 为位置下标提供了一个回退实现,它简单地将调用者视为一个元素的列表。(但关联下标没有这样的回退,因此当应用于不支持它们的物体时,它们会抛出运行时错误。)

say 42[0];    # OUTPUT: «42␤» 
say 42<foo>;  # ERROR: Type Int does not support associative indexing. 

不存在的元素§

当通过下标访问不存在的元素时会发生什么,取决于集合类型本身。标准的 ArrayHash 集合返回其 值类型约束 的类型对象(默认情况下是 Any),除非集合已使用 is default 特性声明,在这种情况下,返回的值将是程序员声明的值。

# no default values specified 
my @array1;     say @array1[10];                   # OUTPUT: «(Any)␤» 
my Int @array2say @array2[10];                   # OUTPUT: «(Int)␤» 
 
my %hash1;      say %hash1<foo>;                   # OUTPUT: «(Any)␤» 
my Int %hash2;  say %hash2<foo>;                   # OUTPUT: «(Int)␤» 
 
# default values specified 
my @array3 is default('--');     say @array3[10];  # OUTPUT: «--␤» 
my Int @array4 is default(17);   say @array4[10];  # OUTPUT: «17␤» 
 
my %hash3 is default('Empty');   say %hash3<foo>;  # OUTPUT: «Empty␤» 
my Int %hash4 is default(4711);  say %hash4<foo>;  # OUTPUT: «4711␤»

但是,其他类型的集合可能对访问不存在元素的下标做出不同的反应。

say (01020)[3];           # OUTPUT: «Nil␤» 
say bag(<a a b b b>)<c>;      # OUTPUT: «0␤» 
say array[uint8].new(12)[2# OUTPUT: «0␤»

要静默跳过下标操作中不存在的元素,请参阅截断切片:v副词。

从末尾§

位置索引从集合的开头开始计数,但也有一个表示法用于通过相对于末尾的位置来寻址元素:*-1 指的是最后一个元素,*-2 指的是倒数第二个元素,依此类推。从 6.d 版本开始,*-0 指的是最后一个元素之后的元素。

my @alphabet = 'A' .. 'Z';
say @alphabet[*-1];  # OUTPUT: «Z␤» 
say @alphabet[*-2];  # OUTPUT: «Y␤» 
say @alphabet[*-3];  # OUTPUT: «X␤» 
@alphabet[*-0= 'þ';
say @alphabet;
#  OUTPUT: «[A B C D E F G H I J K L M N O P Q R S T U V W X Y Z þ]␤»

注意:星号,实际上是一个Whatever,很重要。在 Raku 中,像在许多其他编程语言中那样传递一个裸负整数(例如 @alphabet[-1])会抛出错误。

这里实际发生的是,像 *-1 这样的表达式通过Whatever priming 声明一个代码对象 - 并且 [ ] 下标对被赋予一个代码对象作为索引做出反应,通过用集合的长度作为参数调用它,并使用结果值作为实际索引。换句话说,@alphabet[*-1] 变成了 @alphabet[@alphabet.elems - 1]

这意味着你可以使用依赖于集合大小的任意表达式。

say @array[* div 2];  # select the middlemost element 
say @array[$i % *];   # wrap around a given index ("modular arithmetic") 
say @array-> $size { $i % $size } ];  # same as previous 

切片§

当需要访问集合的多个元素时,有一个快捷方式可以执行多个单独的下标操作:只需在下标中指定一个索引/键列表,即可获得一个元素列表 - 也称为“切片” - 按照相同的顺序。

对于位置切片,你可以混合使用普通索引和从末尾的索引。

my @alphabet = 'a' .. 'z';
say @alphabet[154*-911].raku;  # OUTPUT: «("p", "e", "r", "l")␤»

在上面的 *-number 结构中,* 指示数组的末尾,如上面从末尾部分所述。因此,如果你想获取数组的最后 N 个元素,你将不得不创建一个Range,其中包含它。

(5802..5830).map{.chr} )[*-10..*-5# OUTPUT:  «(ᚽ ᚾ ᚿ ᛀ ᛁ ᛂ)␤»

使用 * 作为范围的最后一个元素,实际上将返回集合的最后几个元素。

对于关联切片,角括号形式通常很方便。

my %color = kiwi => "green"banana => "yellow"cherry => "red";
say %color{"cherry""kiwi"};  # OUTPUT: «(red green)␤» 
say %color<cherry kiwi>;       # OUTPUT: «(red green)␤» 
say %color{*};                 # OUTPUT: «(red yellow green)␤»

请注意,切片由传递到(一维)下标的类型控制,而不是它的长度。特别是,类型可以是以下任何一种:

  • 一个惰性可迭代对象,它在 [ ] 中截断

  • 相应地,一个无限范围将被截断,但一个有限范围会产生一个正常的切片。

  • *(whatever-star),它返回完整的切片(就像指定了所有键/索引一样)。

  • 任何其他对象,它提供单元素访问而不是切片。

  • 可调用对象,可调用对象返回的内容(这会导致递归)。

  • 空,称为禅切片的完整切片。

  • 任何与上述不同的可迭代对象,正常切片。

* 和禅切片(空)之间的显着区别在于,Whatever 星号将导致完全具象化或项目化,而禅切片不会。这两个版本也会去容器化

因此,即使是一个元素的列表也会返回一个切片,而一个裸标量值不会。

say @alphabet[2,];        # OUTPUT: «(c)␤» 
say @alphabet[2,].^name;  # OUTPUT: «List␤» 
 
say @alphabet[2];         # OUTPUT: «c␤» 
say @alphabet[2].^name;   # OUTPUT: «Str␤» 

(关联下标的角括号形式有效,因为单词引用在单个单词的情况下方便地返回一个Str,但在多个单词的情况下返回一个List。)

实际上,(当前维度)下标的列表结构在整个切片操作中都会保留(但 Iterable 的类型不会 - 结果始终只是列表)。

say @alphabet[0, (1..2, (3,))];       # OUTPUT: «(a ((b c) (d)))␤» 
say @alphabet[0, (1..2, [3,])];       # OUTPUT: «(a ((b c) (d)))␤» 
say @alphabet[flat 0, (1..2, (3,))];  # OUTPUT: «(a b c d)␤» 
say flat @alphabet[0, (1..2, (3,))];  # OUTPUT: «(a b c d)␤» 

截断切片§

在切片下标中引用不存在的元素会导致输出List包含未定义的值(或集合所选择的任何其他内容,用于返回不存在的元素)。

my  @letters = <a b c d e f>;
say @letters[3..7];  # OUTPUT: «(d e f (Any) (Any))␤» 

这种行为乍一看可能不直观,但在你想要在当前不存在值的索引处赋值时是可取的。

my  @letters;
say @letters# OUTPUT: «[]␤» 
 
@letters[^10= 'a'..'z';
say @letters# OUTPUT: «[a b c d e f g h i j]␤» 

如果你希望结果切片只包含现有元素,你可以使用:v 副词静默跳过不存在的元素。

my  @letters = <a b c d e f>;
say @letters[3..7]:v;  # OUTPUT: «(d e f)␤» 

通过惰性下标索引集合时的行为与通过其急切对应物索引时的行为不同。当通过惰性下标访问时,结果切片将被截断。

say @letters[lazy 3..7]; # OUTPUT: «(d e f)␤» 
say @letters[     3..*]; # OUTPUT: «(d e f)␤» 

这种行为的存在是为了防止过度生成巨大的、可能无限的List,以及由此产生的内存不足问题。

禅切片§

如果你在不指定任何索引/键的情况下将下标运算符放在对象后面,它只会返回被下标的对象本身。由于它是空的,但返回所有内容,因此被称为禅切片

禅切片不同于传递一个Whatever 星号(它像一个普通切片一样,无论原始对象的类型如何,始终返回一个元素列表)或一个空列表(它返回一个空切片)。

my %bag := (orange => 1apple => 3).Bag;
say %bag<>;    # OUTPUT: «Bag(apple(3) orange)␤» 
say %bag{};    # OUTPUT: «Bag(apple(3) orange)␤» 
say %bag{*};   # OUTPUT: «(1 3)␤» 
say %bag{()};  # OUTPUT: «()␤»

禅切片不会具象化缓存(甚至不Seq),并且只返回调用者。它通常用于将整个数组/哈希插值到字符串中,或用于去容器化

my @words = "cruel""world";
say "Hello, @words[]!";  # OUTPUT: «Hello, cruel world!␤» 
 
my $list = <a b c>;
.say for $list;   # OUTPUT: «(a b c)␤» 
.say for $list<># OUTPUT: «a␤b␤c␤» 
.say for $list[]; # OUTPUT: «a␤b␤c␤» 
 
my @fib = 1,1* + * … *;
say @fib[];                 # OUTPUT: «[...]␤»

多维数组§

下标中的维度用分号隔开,允许混合元素列表和维度。

my @twodim = (<a b c>, (123));
say @twodim;
# OUTPUT: «[(a b c) (1 2 3)]␤» 
 
say @twodim[0,1;1]; # 2nd element of both lists 
# OUTPUT: «(b 2)␤» 
 
my %pantheon = %('Baldr' => 'consort' => 'Nanna' ,
                 'Bragi' => 'consort' => 'Iðunn' ,
                 'Nótt'  => 'consort' => 'Dellingr' );
say %pantheon{'Bragi','Nótt';'consort'}# 'consort' value for both keys 
# OUTPUT: «(Iðunn Dellingr)␤»

多维下标可以与 Whatever 结合使用,将嵌套列表扁平化。

my @toomany = [[<a b>], [12]];
say @toomany;
# OUTPUT: «[[a b] [1 2]]␤» 
 
say @toomany[*;*];
# OUTPUT: «(a b 1 2)␤»

您可以使用任意数量的“扁平化分号”;最多可以扁平化与分号数量相同的嵌套级别。

say [[1,2,[3,4]],[4,5]][*;*];     # OUTPUT: «(1 2 [3 4] 4 5)␤» 
say [[1,2,[3,4]],[4,5]][*;*;*;*]; # OUTPUT: «(1 2 3 4 4 5)␤»

在第一个示例中,Whatever 的数量比级别数量少一个,最深层级别不会被扁平化;在第二个示例中,由于 Whatever 的数量大于级别数量,因此最深层级别会被扁平化。

您可以使用 Whatever 在多维下标中选择范围或“行”。

my @a = [[1,2], [3,4]];
say @a[*;1]; # 2nd element of each sub list 
# OUTPUT: «(2 4)␤» 
my @a = (<1 c 6>, <2 a 4>, <5 b 3>);
say @a.sort{ $_[1} ); # sort by 2nd column 
# OUTPUT: «((2 a 4) (5 b 3) (1 c 6))␤»

修改元素§

自动生成§

下标参与“自动生成”,即数组和哈希在需要时自动生成的过程,因此您可以构建嵌套数据结构,而无需在每个级别预先声明集合类型。

my $beatles;
 
$beatles{"White Album"}[0= "Back in the U.S.S.R.";  # autovivification! 
 
say $beatles.raku;  # OUTPUT: «${"White Album" => $["Back in the U.S.S.R."]}␤» 

$beatles 最初是未定义的,但由于在赋值中使用 { } 进行下标访问,它变成了一个 Hash 对象。类似地,$beatles{"White Album"} 由于在赋值中使用 [ ] 进行下标访问,它变成了一个 Array 对象。

请注意,下标访问本身不会导致自动生成:只有当对下标访问链的结果进行赋值(或其他修改)时才会发生自动生成。

绑定§

下标访问表达式也可以用作绑定语句的左侧。如果被下标访问的集合类型支持,这将用指定的容器替换集合中该“槽位”上自然找到的任何值容器。

内置的 ArrayHash 类型支持此功能,以便允许构建复杂的链接数据结构。

my @a = 10111213;
my $x = 1;
 
@a[2:= $x;  # Bound! (@a[2] and $x refer to the same container now.) 
 
$x++@a[2]++;
 
say @a;  # OUTPUT: «[10 11 3 13]␤» 
say $x;  # OUTPUT: «3␤»

当延迟数据结构是更大数据结构的一部分时,这将特别有用。

my @fib = 1,1* + * … ∞;
my @lucas = 1,3* + * … ∞;
my %sequences;
%sequences<f> := @fib;
%sequences<l> := @lucas;
for %sequences.keys -> $s {
    for ^10 -> $n {
        say %sequences{$s}[100+$n*10]/%sequences{$s}[101+$n*10];
    }
}
# OUTPUT: 0.6180339887498949 times 20. 

在这种情况下,哈希键绑定到延迟生成的序列。绑定意味着任何已计算的状态将由哈希值及其绑定的序列共享,从而使后续元素的计算速度更快。

有关底层机制,请参见 方法 BIND-POS方法 BIND-KEY

副词§

可以使用副词来控制下标访问操作的返回值和可能的副作用;这些副词在相关的下标 运算符 上定义。

注意运算符副词的优先级相对较低,这可能需要您在复合表达式中添加括号。

if $foo || %hash<key>:exists { ... }    # WRONG, tries to adverb the || op 
if $foo || (%hash<key>:exists{ ... }  # correct 
if $foo or %hash<key>:exists { ... }    # also correct 

支持的副词是

:exists§

返回请求的元素是否存在,而不是返回元素的实际值。这可以用来区分具有未定义值的元素和根本不属于集合的元素。

my @foo = Any10;
say @foo[0].defined;    # OUTPUT: «False␤» 
say @foo[0]:exists;     # OUTPUT: «True␤» 
say @foo[2]:exists;     # OUTPUT: «False␤» 
say @foo[02]:exists;  # OUTPUT: «(True False)␤» 
 
my %fruit = apple => Anyorange => 10;
say %fruit<apple>.defined;       # OUTPUT: «False␤» 
say %fruit<apple>:exists;        # OUTPUT: «True␤» 
say %fruit<banana>:exists;       # OUTPUT: «False␤» 
say %fruit<apple banana>:exists# OUTPUT: «(True False)␤»

也可以否定以测试是否存在。

say %fruit<apple banana>:!exists# OUTPUT: «(False True)␤» 

要检查切片中的所有元素是否存在,请使用 all 连接。

if all %fruit<apple orange banana>:exists { ... }

它可以用于多维数组和哈希。

my @multi-dim = 1, [23, [45]];
say @multi-dim[1;2;0]:exists;                # OUTPUT: «True␤» 
say @multi-dim[1;2;5]:exists;                # OUTPUT: «False␤» 
 
my %multi-dim = 1 => { foo => { 3 => 42 } };
say %multi-dim{1;'foo';3}:exists;            # OUTPUT: «True␤» 
say %multi-dim{1;'bar';3}:exists;            # OUTPUT: «False␤»

:exists 可以与 :delete:p/:kv 副词结合使用 - 在这种情况下,行为由这些副词决定,除了任何返回的元素将被替换为相应的 Bool,表示元素存在

有关底层机制,请参见 方法 EXISTS-POS方法 EXISTS-KEY

:delete§

从集合中删除元素,或者如果集合支持,则在返回其值的同时在给定索引处创建一个空洞。

my @tens = 0102030;
say @tens[3]:delete;     # OUTPUT: «30␤» 
say @tens;               # OUTPUT: «[0 10 20]␤» 
 
my %fruit = apple => 5orange => 10banana => 4peach => 17;
say %fruit<apple>:delete;         # OUTPUT: «5␤» 
say %fruit<peach orange>:delete;  # OUTPUT: «(17 10)␤» 
say %fruit;                       # OUTPUT: «{banana => 4}␤»

请注意,将 Nil 赋值给给定索引处的容器将将其恢复为默认值。它不会创建空洞。可以使用 :exists 测试创建的空洞,但迭代不会跳过它们,而是会产生未定义的值。

my @a = 123;
@a[1]:delete;
say @a[1]:exists;
# OUTPUT: «False␤» 
.say for @a;
# OUTPUT: «1␤(Any)␤3␤»

使用副词的否定形式,元素实际上不会被删除。这意味着您可以传递一个标志来使其成为条件。

say %fruit<apple> :delete($flag);  # deletes the element only if $flag is 
                                   # true, but always returns the value. 

它可以与 :exists:p/:kv/:k/:v 副词结合使用 - 在这种情况下,返回值将由这些副词决定,但元素也会同时被删除。

有关底层机制,请参阅 方法 DELETE-POS方法 DELETE-KEY

您也可以在关联类型对象上使用这些副词,但它实际上不会做任何事情;它也会返回 Nil

say Hash<foo>:delete# OUTPUT: «Nil␤» 

该副词也可以用于延迟数组。

my @lazy-array = lazy 111121 ... 10**100;
@lazy-array[2**24]:delete;

:p§

Pair 的形式返回元素的索引/键和值,并静默跳过不存在的元素。

my  @tens = 0102030;
say @tens[1]:p;        # OUTPUT: «1 => 10␤» 
say @tens[042]:p;  # OUTPUT: «(0 => 0 2 => 20)␤» 
 
my  %month = Jan => 1Feb => 2Mar => 3;
say %month<Feb>:p;          # OUTPUT: «Feb => 2␤» 
say %month<Jan Foo Mar>:p;  # OUTPUT: «(Jan => 1 Mar => 3)␤»

如果您 *不想* 跳过不存在的元素,请使用否定形式。

say %month<Jan Foo Mar>:!p;  # OUTPUT: «(Jan => 1 Foo => (Any) Mar => 3)␤» 

它可以与 :exists:delete 副词结合使用。

另请参阅 pairs 例程。

:kv§

List 的形式返回元素的索引/键和值,并静默跳过不存在的元素。当用于 切片 时,返回值是交错键和值的单个扁平列表。

my  @tens = 0102030;
say @tens[1]:kv;        # OUTPUT: «(1 10)␤» 
say @tens[042]:kv;  # OUTPUT: «(0 0 2 20)␤» 
 
my  %month = Jan => 1Feb => 2Mar => 3;
say %month<Feb>:kv;          # OUTPUT: «(Feb 2)␤» 
say %month<Jan Foo Mar>:kv;  # OUTPUT: «(Jan 1 Mar 3)␤»

如果您 *不想* 跳过不存在的元素,请使用否定形式。

say %month<Jan Foo Mar>:!kv;  # OUTPUT: «(Jan 1 Foo (Any) Mar 3)␤» 

该副词通常用于迭代切片。

for %month<Feb Mar>:kv -> $month$i {
    say "$month had {Date.new(2015$i1).days-in-month} days in 2015"
}

它可以与 :exists:delete 副词结合使用。

另请参阅 kv 例程。

:k§

仅返回元素的索引/键,而不是其值,并静默跳过不存在的元素。

my @tens = 0102030;
say @tens[1]:k;        # OUTPUT: «1␤» 
say @tens[042]:k;  # OUTPUT: «(0 2)␤» 
 
my %month = Jan => 1Feb => 2Mar => 3;
say %month<Feb>:k;          # OUTPUT: «Feb␤» 
say %month<Jan Foo Mar>:k;  # OUTPUT: «(Jan Mar)␤»

如果您 *不想* 跳过不存在的元素,请使用否定形式。

say %month<Jan Foo Mar>:!k;  # OUTPUT: «(Jan Foo Mar)␤» 

另请参阅 keys 例程。

:v§

返回元素的裸值(而不是可能返回可变值容器),并静默跳过不存在的元素。

my @tens = 0102030;
say @tens[1]:v;        # OUTPUT: «10␤» 
say @tens[042]:v;  # OUTPUT: «(0, 20)␤» 
@tens[3= 31;         # OK 
@tens[3]:v = 31;       # ERROR, Cannot modify an immutable Int (31) 
 
my %month = Jan => 1Feb => 2Mar => 3;
say %month<Feb>:v;          # OUTPUT: «2␤» 
say %month<Jan Foo Mar>:v;  # OUTPUT: «(1 3)␤» 

如果您 *不想* 跳过不存在的元素,请使用否定形式。

say %month<Jan Foo Mar>:!v;  # OUTPUT: «(1 (Any) 3)␤» 

另请参阅 values 例程。

自定义类型§

本页描述的索引接口并非专为 Raku 的内置集合类型而设计 - 您可以(也应该)将它们重新用于任何希望通过索引或键访问数据的自定义类型。

您不必手动重载 postcircumfix [ ]postcircumfix { } 运算符并重新实现所有它们的魔法,以实现这一点 - 相反,您可以依赖于这样一个事实,即它们的标准实现会分派到幕后定义明确的一组低级方法。例如

当您编写这将在幕后被调用
%foo<aa>%foo.AT-KEY("aa")
%foo<aa>:delete%foo.DELETE-KEY("aa")
@foo[3, 4, 5]@foo.AT-POS(3), @foo.AT-POS(4), @foo.AT-POS(5)
@foo[*-1]@foo.AT-POS(@foo.elems - 1)

因此,为了使索引起作用,您只需要为您的自定义类型实现或委托这些低级方法(下面详细介绍)。

如果您这样做,您还应该让您的类型组合 PositionalAssociative 角色,分别。这本身不会添加任何功能,但会宣布(并且可以用来检查)该类型是否实现了相应的索引接口。

自定义类型示例§

想象一个 HTTP::Header 类型,尽管它是一个具有特殊行为的自定义类,但可以像哈希一样被索引。

my $request = HTTP::Request.new(GET => "raku.org");
say $request.header.^name;  # OUTPUT: «HTTP::Header␤» 
 
$request.header<Accept> = "text/plain";
$request.header{'Accept-' X~ <Charset Encoding Language>} = <utf-8 gzip en>;
$request.header.push('Accept-Language' => "fr");  # like .push on a Hash 
 
say $request.header<Accept-Language>.raku;  # OUTPUT: «["en", "fr"]␤» 
 
my $rawheader = $request.header.Str;  # stringify according to HTTP spec 

实现此类的一个简单方法是赋予它一个 Hash 类型的属性,并将所有与索引和迭代相关的功能委托给该属性(使用自定义类型约束来确保用户不会向其中插入任何无效内容)。

class HTTP::Header does Associative {
    subset StrOrArrayOfStr where Str | ( Array & {.all ~~ Str} );
 
    has %!fields of StrOrArrayOfStr
                 handles <AT-KEY EXISTS-KEY DELETE-KEY push
                          iterator list kv keys values>;
 
    method Str { #`[not shown, for brevity] }
}

但是,HTTP 标头字段名称应该是不区分大小写的(并且首选驼峰式命名)。我们可以通过将 *-KEYpush 方法从 handles 列表中取出,并像这样分别实现它们来适应这一点。

method AT-KEY     ($keyis rw { %!fields{normalize-key $key}        }
method EXISTS-KEY ($key)       { %!fields{normalize-key $key}:exists }
method DELETE-KEY ($key)       { %!fields{normalize-key $key}:delete }
method push(*@_{ #`[not shown, for brevity] }
 
sub normalize-key ($key{ $key.subst(/\w+/*.tc:g}

请注意,索引 %!fields 返回一个适当的 rw 容器,我们的 AT-KEY 可以简单地传递它。

但是,我们可能更喜欢对用户输入不太严格,而是自己处理字段值的清理。在这种情况下,我们可以删除 %!fields 上的 StrOrArrayOfStr 类型约束,并将我们的 AT-KEY 实现替换为一个返回自定义 Proxy 容器的实现,该容器负责在赋值时清理值。

multi method AT-KEY (::?CLASS:D: $keyis rw {
    my $element := %!fields{normalize-key $key};
 
    Proxy.new(
        FETCH => method () { $element },
 
        STORE => method ($value{
            $element = do given $value».split(/',' \s+/).flat {
                when 1  { .[0}    # a single value is stored as a string 
                default { .Array }  # multiple values are stored as an array 
            }
        }
    );
}

请注意,将方法声明为multi并将其限制为:D(已定义的调用者)可以确保未定义的情况将传递给由Any(参与自动活化)提供的默认实现。

用于实现位置下标的方法§

为了使基于索引的下标(通过postcircumfix [ ])对您的自定义类型起作用,您应该至少实现elemsAT-POSEXISTS-POS - 以及可选地实现下面详细介绍的其他方法。

方法 elems§

multi method elems(::?CLASS:D:)

预期返回一个数字,指示对象中存在多少个可下标元素。用户可以直接调用它,并且当从末尾索引元素时(例如@foo[*-1]),postcircumfix [ ]也会调用它。

如果未实现,您的类型将从Any继承默认实现,该实现始终为已定义的调用者返回1 - 这很可能不是您想要的。因此,如果无法知道您的位置类型的元素数量,请添加一个实现,该实现会faildie,以避免静默地执行错误的操作。

方法 AT-POS§

multi method AT-POS (::?CLASS:D: $index)

预期返回位置$index处的元素。这是postcircumfix [ ]通常调用的方法。

如果您希望元素可变(就像它们对于内置的Array类型一样),您必须确保以项容器的形式返回它,该容器在读取时计算为元素的值,并在分配时更新它。请记住使用return-rwis rw例程特征来使它工作;请参阅示例

方法 EXISTS-POS§

multi method EXISTS-POS (::?CLASS:D: $index)

预期返回一个布尔值,指示位置$index处是否存在元素。这是postcircumfix [ ]在像@foo[42]:exists这样调用时调用的方法。

元素的“存在”意味着什么,取决于您的类型。

如果您不实现此方法,您的类型将从Any继承默认实现,该实现为 0 返回 True,为任何其他索引返回 False - 这可能不是您想要的。因此,如果无法检查您的类型的元素是否存在,请添加一个实现,该实现会faildie,以避免静默地执行错误的操作。

方法 DELETE-POS§

multi method DELETE-POS (::?CLASS:D: $index)

预期删除位置$index处的元素,并返回它具有的值。这是postcircumfix [ ]在像@foo[42]:delete这样调用时调用的方法。

“删除”元素意味着什么,取决于您的类型。

实现此方法是可选的;如果您不实现,尝试从此类型对象中删除元素的用户将收到相应的错误消息。

方法 ASSIGN-POS§

multi method ASSIGN-POS (::?CLASS:D: $index$new)

预期将位置$index处的元素设置为值$new。实现此方法完全是可选的;如果您不实现,self.AT-POS($index) = $new将被使用,如果您实现,您应该确保它具有相同的效果。

这是作为一种可选的性能优化,以便像@numbers[5] = "five"这样的简单赋值可以在不调用AT-POS(这将不得不创建并返回一个可能很昂贵的容器对象)的情况下进行操作。

请注意,实现ASSIGN-POS不会让您免于将AT-POS设为rw方法,因为像@numbers[5]++这样的不太简单的赋值/修改仍然会使用AT-POS

方法 BIND-POS§

multi method BIND-POS (::?CLASS:D: $index, \new)

预期将值或容器new绑定到位置$index处的槽位,替换任何自然存在于那里的容器。当您编写以下内容时,就会调用它

my $x = 10;
@numbers[5:= $x;

通用的Array类支持此功能,以便允许构建复杂的链接数据结构,但对于更特定于领域的类型,它可能没有意义,因此不要感到有必要实现它。如果您不实现,当用户尝试绑定到此类型对象的某个位置槽位时,他们将收到相应的错误消息。

方法 STORE§

method STORE (::?CLASS:D: \values:$INITIALIZE)

只有在您想支持此语法时才应提供此方法

my @a is Foo = 1,2,3;

它用于绑定您对 Positional 角色的实现。

STORE 应该接受用于(重新)初始化对象的 value。可选的命名参数将在首次在对象上调用该方法时包含 True 值。它应该返回调用者。

role Logger { method logStr $msg{}}
 
class ConsoLogger does Logger { method log ( Str $msg ) { "❢ $msg".say }}
 
class DNA {
    has $.chain;
    has Logger $!logger;
 
    submethod BUILD:$chain:$logger = ConsoLogger.new() ) {}
 
    method STORE (Str $chain where {
            /^^ <[ACGT]>+ $$ / and
            .chars %% 3
        },
        :$INITIALIZE --> DNA{
 
        if ($INITIALIZE{
            $!logger = ConsoLogger.new();
            $!logger.log"Initialized" );
        }
 
        $!chain  := $chain;
        $!logger.log("Change value to $chain" );
        self
    }
 
    method Str(::?CLASS:D:{ return $!chain.comb.rotor(3).map*.join("")).join("|"}
};
 
my @string is DNA = 'GAATCC';    # OUTPUT: «❢ Initialized␤❢ Change value to GAATCC␤» 
say ~@string;                    # OUTPUT: «GAA|TCC␤» 
@string = 'ACGTCG';              # OUTPUT: «❢ Change value to ACGTCG␤» 
say  ~@string;                   # OUTPUT: «ACG|TCG␤» 

此代码考虑了 $INITIALIZE 的值,该值仅在首次将值分配给使用 is 语法声明的变量时设置为 True;例如,在这种情况下,我们可能需要初始化任何注入的依赖项。STORE 方法应该设置 self 变量并在所有情况下返回它,包括变量已经初始化时;但是,只有在第一种情况下,我们需要初始化我们在本例中使用的日志记录器。

INITIALIZE 标志的存在也可以用于创建不可变数据结构

class A {
    has @.foo handles <Str gist raku>;
    multi method STORE(*@!foo:$INITIALIZE!{ }
    multi method STORE(|) { die "Immutable" }
}
 
my @a is A = 1,2,3,4;
say @a;        # OUTPUT: «[1,2,3,4]␤» 
@a = 4,5,6,7;  # dies: Immutable 

用于关联下标的实现方法§

为了使基于键的下标通过 postcircumfix { } 对您的自定义类型起作用,您应该至少实现 AT-KEYEXISTS-KEY - 以及可选的其他方法,如下所述。

方法 AT-KEY§

multi method AT-KEY (::?CLASS:D: $key)

预期返回与 $key 关联的元素。这是 postcircumfix { } 通常调用的方法。

如果您希望元素可变(就像它们对于内置的 Hash 类型一样),您必须确保以项容器的形式返回它,该容器在读取时计算为元素的值,并在分配时更新它。请记住使用 return-rwis rw 例程特征来使它起作用;请参阅 示例

另一方面,如果您希望您的集合为只读,请随时直接返回非容器值。

方法 EXISTS-KEY§

multi method EXISTS-KEY (::?CLASS:D: $key)

预期返回一个 Bool,指示是否与 $key 关联的元素。这是 postcircumfix { } 在像 %foo<aa>:exists 这样调用时调用的方法。

元素的“存在”意味着什么,取决于您的类型。

如果您没有实现它,您的类型将从 Any 继承默认实现,该实现始终返回 False - 这可能不是您想要的。因此,如果无法检查您的类型的元素是否存在,请添加一个实现 faildie 的实现,以避免静默地执行错误的操作。

方法 DELETE-KEY§

multi method DELETE-KEY (::?CLASS:D: $key)

预期删除与 $key 关联的元素,并返回它具有的值。这是 postcircumfix { } 在像 %foo<aa>:delete 这样调用时调用的方法。

"删除" 元素的含义取决于您的类型 - 尽管它通常会导致 EXISTS-KEY 对该键变为 False

实现此方法是可选的;如果您不实现,尝试从此类型对象中删除元素的用户将收到相应的错误消息。

方法 ASSIGN-KEY§

multi method ASSIGN-KEY (::?CLASS:D: $key$new)

预期将与 $key 关联的元素设置为值 $new。实现这一点完全是可选的;如果您没有,self.AT-KEY($key) = $new 将被使用,如果您有,您应该确保它具有相同的效果。

这旨在作为一种可选的性能优化,以便简单的赋值 %age<Claire> = 29 可以操作,而无需调用 AT-KEY(它必须创建并返回一个可能很昂贵的容器对象)。

请注意,实现 ASSIGN-KEY 不会 使您免于将 AT-KEY 设为 rw 方法,因为不太简单的赋值/修改(如 %age<Claire>++)仍然会使用 AT-KEY

方法 BIND-KEY§

multi method BIND-KEY (::?CLASS:D: $key, \new)

预期将值或容器 new 绑定到与 $key 关联的槽,替换那里自然找到的任何容器。当您编写以下内容时,就会调用它

my $x = 10;
%age<Claire> := $x;

通用 Hash 类支持这一点,以便允许构建复杂的链接数据结构,但对于更特定于域的类型,它可能没有意义,因此不要感到被迫实现它。如果您没有,用户将在尝试绑定到此类型对象的关联槽时收到相应的错误消息。

方法 STORE§

method STORE (::?CLASS:D: \values:$INITIALIZE)

只有在您想要支持

my %h is Foo = => 42=> 666;

绑定您实现的 Associative 角色的语法时,才应提供此方法。

应接受用于(重新)初始化对象的数值,这些数值可以由 Pair 或单独的键值对组成。可选的命名参数将在首次调用对象上的方法时包含一个 True 值。应返回调用者。