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
角色。