constant IterationEnd
Iterator 是一个可以生成或提供序列元素的对象。用户通常不必关心迭代器,它们的用法隐藏在迭代 API 后面,例如 for @list { }、map、grep、head、tail、skip 和带有 .[$idx] 的列表索引。
主 API 是 pull-one 方法,它要么返回下一个值,要么在没有更多元素可用时返回哨兵值 IterationEnd。每个实现 Iterator 的类必须提供一个 pull-one 方法。所有其他非可选 Iterator API 方法都是根据 pull-one 实现的,但也可以被消耗类覆盖以提高性能或出于其他原因。还有一些可选的 Iterator API 方法,只有在它们由消耗类实现时才会被调用:这些不是由 Iterator 角色实现的。
IterationEnd§
迭代器只允许对整个序列进行一次迭代。一旦生成了 IterationEnd,就禁止尝试获取更多数据,并且这样做时的行为是未定义的。例如,以下 Seq 在正常使用下不会导致调用 die,因为在返回 IterationEnd 后永远不会调用 pull-one
is Arraymy := SkippingArray.new;.append: 1, Any, 3, Int, 5, Mu, 7;for -> ,# OUTPUT: «[1 3][5 7]»
在程序中唯一有效的哨兵值 IterationEnd 用法是与迭代器 API 中的方法结果进行恒等比较(使用 =:=)。任何其他行为都是未定义的并且依赖于实现。例如,将它用作要迭代的列表的一部分可能会导致循环结束或不会结束。
.say for ["foo",IterationEnd, "baz"]; # OUTPUT: «foo»say ["foo",IterationEnd, "baz"].map: "«" ~ * ~ "»";# OUTPUT: «(«foo» «IterationEnd» «baz»)»
请记住,IterationEnd 是一个常量,因此,如果您要将其与变量的值进行比较,则此变量必须绑定,而不是赋值。直接与 pull-one 的输出进行比较将起作用。
my = (1,2).iterator;.pull-one for ^2;say .pull-one =:= IterationEnd; # OUTPUT: «True»
但是,如果我们使用变量并且对其进行赋值,则结果将不正确
my = (1,2).iterator;.pull-one for ^2;my = .pull-one;say =:= IterationEnd; # OUTPUT: «False»
因此,我们将不得不绑定变量以使其工作
my := .pull-one;say =:= IterationEnd; # OUTPUT: «True»
方法§
pull-one 方法§
method pull-one(Iterator: --> Mu)
此方法存根确保实现 Iterator 角色的类提供名为 pull-one 的方法。
pull-one 方法应该尽可能生成并返回下一个值,或者在无法生成更多值时返回哨兵值 IterationEnd。
my = (1 .. 3).iterator;say .pull-one; # OUTPUT: «1»say .pull-one; # OUTPUT: «2»say .pull-one; # OUTPUT: «3»say .pull-one.raku; # OUTPUT: «IterationEnd»
作为其用途的一个更直观的示例,这里有一个倒数迭代器以及 for 循环的简单子例程重新实现。
# works the same as (10 ... 1, 'lift off')does Iteratorsub for( Iterable , --> Nil )for( Seq.new(CountDown.new), ); # OUTPUT: «10987654321lift off»
使用 while 或 until 以及无 sigil 变量会更习惯用语。
until IterationEnd =:= (my \pulled = .pull-one)
push-exactly 方法§
method push-exactly(Iterator: , int --> Mu)
应该生成 $count 个元素,并针对每个元素调用 $target.push($value)。
如果从迭代器中获得的元素少于 $count 个,它应该返回哨兵值 IterationEnd。否则,它应该返回 $count。
my ;say (1 .. ∞).iterator.push-exactly(, 3); # OUTPUT: «3»say ; # OUTPUT: «[1 2 3]»
Iterator 角色根据 pull-one 实现此方法。一般来说,此方法不是打算由最终用户直接调用的,最终用户应该在混合迭代器角色的类中实现它。例如,此类实现了该角色
does Iterable does Iterator;my := DNA.new("AAGCCT");for -> , , ; # Does not enter the loopmy := DNA.new("CAGCGGAAGCCT");for -> ,
此代码将 DNA 链分组为三元组(通常称为密码子),并在循环中请求时返回这些密码子;如果请求的密码子过多,如在第一个案例中for $b -> $a, $b, $c,它根本不会进入循环,因为push-exactly将返回IterationEnd,因为它无法满足对正好 3 个密码子的请求。然而,在第二个案例中,它在循环的每个迭代中请求正好两个密码子;push-exactly被调用,其中循环变量的数量作为$count变量。
方法 push-at-least§
method push-at-least(Iterator: , int --> Mu)
应生成至少$count个元素,并为每个元素调用$target.push($value)。
如果从迭代器中获得的元素少于 $count 个,它应该返回哨兵值 IterationEnd。否则,它应该返回 $count。
具有副作用的迭代器应生成正好$count个元素;没有副作用的迭代器(例如Range迭代器)可以生成更多元素以实现更好的性能。
my ;say (1 .. ∞).iterator.push-at-least(, 10); # OUTPUT: «10»say ; # OUTPUT: «[1 2 3 4 5 6 7 8 9 10]»
Iterator 角色通过pull-one实现此方法。通常,也不打算像上面的示例中那样直接调用它。如果对这个默认实现不满意,可以使用这个角色的人可以实现它。有关示例实现,请参见push-exactly的文档。
方法 push-all§
method push-all(Iterator: )
应生成迭代器中的所有元素并将它们推送到$target。
my ;say (1 .. 1000).iterator.push-all(); # All 1000 values are pushed
Iterator角色通过push-at-least实现此方法。与其他push-*方法一样,它主要用于实现此角色的开发人员。在将具有此角色的对象分配给数组时调用push-all,例如,如下例所示
does Iterable does Iterator;my := DNA.new("AAGCCT");my = ;say ; # OUTPUT: «[(A A G) (C C T)]»
实现的push-all方法将三个氨基酸表示形式列表推送到目标迭代器;当我们将$b分配给@dna-array时,会在幕后调用此方法。
方法 push-until-lazy§
method push-until-lazy(Iterator: --> Mu)
应生成值,直到它认为自己是惰性的,并将它们推送到$target。
如果is-lazy返回 True 值,则 Iterator 角色将此方法实现为无操作;如果不是,则将其实现为push-all的同义词。
这主要适用于包含其他迭代器的迭代器,其中一些可能是惰性的,而另一些则不是。
方法 is-deterministic§
method is-deterministic(Iterator: --> Bool)
对于给定源后将始终按相同顺序生成值的迭代器,应返回True。
当确定总是按相同顺序生成值时,内置操作可以执行某些优化。一些示例
say (1..10).iterator.is-deterministic; # OUTPUT: «True»say (1..10).roll(5).iterator.is-deterministic; # OUTPUT: «False»say %(a => 42, b => 137).iterator.is-deterministic; # OUTPUT: «False»
Iterator 角色实现此方法返回True,指示一个将始终按相同顺序生成值的迭代器。
方法 is-monotonically-increasing§
method is-monotonically-increasing(Iterator: --> Bool)
对于给定源后将始终按递增值(根据cmp语义)生成值的迭代器,应返回True。
当确定值始终按升序生成时,内置操作可以执行某些优化。一些示例
say (1..10).iterator.is-monotonically-increasing; # OUTPUT: «True»say (1..10).roll(5).iterator.is-monotonically-increasing; # OUTPUT: «False»say %(a => 42, b => 137).iterator.is-monotonically-increasing; # OUTPUT: «False»
Iterator 角色实现此方法,返回 False,表示一个迭代器,不会生成具有递增值的的值。
方法 is-lazy§
method is-lazy(Iterator: --> Bool)
对于认为自己是惰性的迭代器,应返回 True,否则返回 False。
内置操作知道它们可以生成无限多个值,在此处返回 True,例如 (1..6).roll(*)。
say (1 .. 100).iterator.is-lazy; # OUTPUT: «False»say (1 .. ∞).iterator.is-lazy; # OUTPUT: «True»
Iterator 角色实现此方法,返回 False,表示一个非惰性迭代器。
方法 sink-all§
method sink-all(Iterator: --> IterationEnd)
应仅为了生成值的副作用而耗尽迭代器,而无需以任何方式实际保存它们。应始终返回 IterationEnd。如果生成值没有任何副作用,则可以由消耗类实现它,使其成为一个虚拟空操作。
say (1 .. 1000).iterator.sink-all; # OUTPUT: «IterationEnd»
Iterator 角色将此方法实现为一个循环,该循环调用 pull-one,直到它耗尽。
方法 skip-one§
method skip-one(Iterator: --> Mu)
应跳过生成一个值。如果跳过成功,则返回值应为真值,如果没有任何要跳过的值,则返回值应为假值
my = <a b>.iterator;say .skip-one; say .pull-one; say .skip-one# OUTPUT: «1b0»
Iterator 角色将此方法实现为调用 pull-one,并返回获得的值是否不是 IterationEnd。
方法 skip-at-least§
method skip-at-least(Iterator: , int --> Mu)
应跳过生成 $to-skip 个值。如果跳过成功,则返回值应为真值,如果要跳过的值不足,则返回值应为假值
my = <a b c>.iterator;say .skip-at-least(2); say .pull-one; say .skip-at-least(20);# OUTPUT: «1c0»
Iterator 角色将此方法实现为一个循环,该循环调用 skip-one,并返回它是否返回了足够次数的真值。
方法 skip-at-least-pull-one§
method skip-at-least-pull-one(Iterator: , int --> Mu)
应跳过生成 $to-skip 个值,如果迭代器仍未耗尽,则生成并返回下一个值。如果迭代器在任何时候都已耗尽,则应返回 IterationEnd
my = <a b c>.iterator;say .skip-at-least-pull-one(2);say .skip-at-least-pull-one(20) =:= IterationEnd;# OUTPUT: «cTrue»
Iterator 角色将此方法实现为调用 skip-at-least,然后在尚未耗尽的情况下调用 pull-one。
预测迭代器§
如果 Iterator 可以知道它在不实际生成它们的情况下还可以生成多少个值,请参阅 PredictiveIterator 角色。
类型图§
Iterator 的类型关系