constant IterationEnd
role Iterator { }

Iterator 是一个可以生成或提供序列元素的对象。用户通常不必关心迭代器,它们的用法隐藏在迭代 API 后面,例如 for @list { }mapgrepheadtailskip 和带有 .[$idx] 的列表索引。

主 API 是 pull-one 方法,它要么返回下一个值,要么在没有更多元素可用时返回哨兵值 IterationEnd。每个实现 Iterator 的类必须提供一个 pull-one 方法。所有其他非可选 Iterator API 方法都是根据 pull-one 实现的,但也可以被消耗类覆盖以提高性能或出于其他原因。还有一些可选的 Iterator API 方法,只有在它们由消耗类实现时才会被调用:这些不是由 Iterator 角色实现的。

IterationEnd§

迭代器只允许对整个序列进行一次迭代。一旦生成了 IterationEnd,就禁止尝试获取更多数据,并且这样做时的行为是未定义的。例如,以下 Seq 在正常使用下不会导致调用 die,因为在返回 IterationEnd 后永远不会调用 pull-one

class SkippingArray is Array {
    # skip all undefined values while iterating 
    method iterator {
        class :: does Iterator {
            has $.index is rw = 0;
            has $.array is required;
            method pull-one {
                $.index++ while !$.array.AT-POS($.index).defined && $.array.elems > $.index;
                $.array.elems > $.index ?? $.array.AT-POS($.index++!! IterationEnd
            }
        }.new(array => self)
    }
}
 
my @a := SkippingArray.new;
 
@a.append: 1Any3Int5Mu7;
 
for @a -> $a$b {
    say [$a$b];
}
 
# 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 $it = (1,2).iterator;
$it.pull-one for ^2;
say $it.pull-one =:= IterationEnd# OUTPUT: «True␤» 

但是,如果我们使用变量并且对其进行赋值,则结果将不正确

my $it = (1,2).iterator;
$it.pull-one for ^2;
my $is-it-the-end = $it.pull-one;
say $is-it-the-end =:= IterationEnd# OUTPUT: «False␤» 

因此,我们将不得不绑定变量以使其工作

my $is-it-the-end := $it.pull-one;
say $is-it-the-end =:= IterationEnd# OUTPUT: «True␤» 

方法§

pull-one 方法§

method pull-one(Iterator:D: --> Mu)

此方法存根确保实现 Iterator 角色的类提供名为 pull-one 的方法。

pull-one 方法应该尽可能生成并返回下一个值,或者在无法生成更多值时返回哨兵值 IterationEnd

my $i = (1 .. 3).iterator;
say $i.pull-one;       # OUTPUT: «1␤» 
say $i.pull-one;       # OUTPUT: «2␤» 
say $i.pull-one;       # OUTPUT: «3␤» 
say $i.pull-one.raku;  # OUTPUT: «IterationEnd␤»

作为其用途的一个更直观的示例,这里有一个倒数迭代器以及 for 循环的简单子例程重新实现。

# works the same as (10 ... 1, 'lift off') 
class CountDown does Iterator {
    has Int:D $!current = 10;
 
    method pull-one ( --> Mu ) {
        my $result = $!current--;
        if $result ==  0 { return 'lift off' }
        if $result == -1 { return IterationEnd }
 
        # calling .pull-one again after it returns IterationEnd is undefined 
        if $result <= -2 {
            # so for fun we will give them nonsense data 
            return (1..10).pick;
        }
 
        return $result;
    }
}
 
sub forIterable:D $sequence&do --> Nil ) {
    my Iterator:D $iterator = $sequence.iterator;
 
    loop {
        # must bind the result so that =:= works 
        my Mu $pulled := $iterator.pull-one;
 
        # always check the result and make sure that .pull-one 
        # is not called again after it returns IterationEnd 
        if $pulled =:= IterationEnd { last }
 
        do$pulled );
    }
}
 
forSeq.new(CountDown.new), &say );  # OUTPUT: «10␤9␤8␤7␤6␤5␤4␤3␤2␤1␤lift off␤»

使用 whileuntil 以及无 sigil 变量会更习惯用语。

until IterationEnd =:= (my \pulled = $iterator.pull-one{
    dopulled );
}

push-exactly 方法§

method push-exactly(Iterator:D: $targetint $count --> Mu)

应该生成 $count 个元素,并针对每个元素调用 $target.push($value)

如果从迭代器中获得的元素少于 $count 个,它应该返回哨兵值 IterationEnd。否则,它应该返回 $count

my @array;
say (1 .. ∞).iterator.push-exactly(@array3); # OUTPUT: «3␤» 
say @array# OUTPUT: «[1 2 3]␤»

Iterator 角色根据 pull-one 实现此方法。一般来说,此方法不是打算由最终用户直接调用的,最终用户应该在混合迭代器角色的类中实现它。例如,此类实现了该角色

class DNA does Iterable does Iterator {
    has $.chain;
    has Int $!index = 0;
 
    method new ($chain where {
                       $chain ~~ /^^ <[ACGT]>+ $$ / and
                       $chain.chars %% 3 } ) {
        self.bless:$chain );
    }
 
    method iterator( ){ self }
 
    method pull-one--> Mu){
      if $!index < $.chain.chars {
         my $codon = $.chain.comb.rotor(3)[$!index div 3];
         $!index += 3;
         return $codon;
      } else {
        return IterationEnd;
      }
    }
 
    method push-exactly(Iterator:D: $targetint $count --> Mu{
        return IterationEnd if $.chain.elems / 3 < $count;
        for ^($count{
            $target.push: $.chain.comb.rotor(3)[ $_ ];
        }
    }
 
};
 
my $b := DNA.new("AAGCCT");
for $b -> $a$b$c { say "Never mind" }# Does not enter the loop 
my $þor := DNA.new("CAGCGGAAGCCT");
for $þor -> $first$second {
    say "Coupled codons: $first$second";
    # OUTPUT: «Coupled codons: C A G, C G G␤Coupled codons: A A G, C C T␤» 
}

此代码将 DNA 链分组为三元组(通常称为密码子),并在循环中请求时返回这些密码子;如果请求的密码子过多,如在第一个案例中for $b -> $a, $b, $c,它根本不会进入循环,因为push-exactly将返回IterationEnd,因为它无法满足对正好 3 个密码子的请求。然而,在第二个案例中,它在循环的每个迭代中请求正好两个密码子;push-exactly被调用,其中循环变量的数量作为$count变量。

方法 push-at-least§

method push-at-least(Iterator:D: $targetint $count --> Mu)

应生成至少$count个元素,并为每个元素调用$target.push($value)

如果从迭代器中获得的元素少于 $count 个,它应该返回哨兵值 IterationEnd。否则,它应该返回 $count

具有副作用的迭代器应生成正好$count个元素;没有副作用的迭代器(例如Range迭代器)可以生成更多元素以实现更好的性能。

my @array;
say (1 .. ∞).iterator.push-at-least(@array10); # OUTPUT: «10␤» 
say @array# OUTPUT: «[1 2 3 4 5 6 7 8 9 10]␤»

Iterator 角色通过pull-one实现此方法。通常,也不打算像上面的示例中那样直接调用它。如果对这个默认实现不满意,可以使用这个角色的人可以实现它。有关示例实现,请参见push-exactly的文档

方法 push-all§

method push-all(Iterator:D: $target)

应生成迭代器中的所有元素并将它们推送到$target

my @array;
say (1 .. 1000).iterator.push-all(@array); # All 1000 values are pushed

Iterator角色通过push-at-least实现此方法。与其他push-*方法一样,它主要用于实现此角色的开发人员。在将具有此角色的对象分配给数组时调用push-all,例如,如下例所示

class DNA does Iterable does Iterator {
    has $.chain;
    has Int $!index = 0;
 
    method new ($chain where {
                       $chain ~~ /^^ <[ACGT]>+ $$ / and
                       $chain.chars %% 3 } ) {
        self.bless:$chain );
    }
 
    method iterator( ){ self }
    method pull-one--> Mu){
      if $!index < $.chain.chars {
         my $codon = $.chain.comb.rotor(3)[$!index div 3];
         $!index += 3;
         return $codon;
      } else {
        return IterationEnd;
      }
    }
 
    method push-all(Iterator:D: $target{
        for $.chain.comb.rotor(3-> $codon {
            $target.push: $codon;
        }
    }
 
};
 
my $b := DNA.new("AAGCCT");
my @dna-array = $b;
say @dna-array# OUTPUT: «[(A A G) (C C T)]␤» 

实现的push-all方法将三个氨基酸表示形式列表推送到目标迭代器;当我们将$b分配给@dna-array时,会在幕后调用此方法。

方法 push-until-lazy§

method push-until-lazy(Iterator:D: $target --> Mu)

应生成值,直到它认为自己是惰性的,并将它们推送到$target

如果is-lazy返回 True 值,则 Iterator 角色将此方法实现为无操作;如果不是,则将其实现为push-all的同义词。

这主要适用于包含其他迭代器的迭代器,其中一些可能是惰性的,而另一些则不是。

方法 is-deterministic§

method is-deterministic(Iterator:D: --> Bool:D)

对于给定源后将始终按相同顺序生成值的迭代器,应返回True

当确定总是按相同顺序生成值时,内置操作可以执行某些优化。一些示例

say (1..10).iterator.is-deterministic;              # OUTPUT: «True␤» 
say (1..10).roll(5).iterator.is-deterministic;      # OUTPUT: «False␤» 
say %(=> 42=> 137).iterator.is-deterministic# OUTPUT: «False␤»

Iterator 角色实现此方法返回True,指示一个将始终按相同顺序生成值的迭代器。

方法 is-monotonically-increasing§

method is-monotonically-increasing(Iterator:D: --> Bool:D)

对于给定源后将始终按递增值(根据cmp语义)生成值的迭代器,应返回True

当确定值始终按升序生成时,内置操作可以执行某些优化。一些示例

say (1..10).iterator.is-monotonically-increasing;              # OUTPUT: «True␤» 
say (1..10).roll(5).iterator.is-monotonically-increasing;      # OUTPUT: «False␤» 
say %(=> 42=> 137).iterator.is-monotonically-increasing# OUTPUT: «False␤»

Iterator 角色实现此方法,返回 False,表示一个迭代器,不会生成具有递增值的的值。

方法 is-lazy§

method is-lazy(Iterator:D: --> Bool:D)

对于认为自己是惰性的迭代器,应返回 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:D: --> IterationEnd)

应仅为了生成值的副作用而耗尽迭代器,而无需以任何方式实际保存它们。应始终返回 IterationEnd。如果生成值没有任何副作用,则可以由消耗类实现它,使其成为一个虚拟空操作。

say (1 .. 1000).iterator.sink-all;  # OUTPUT: «IterationEnd␤»

Iterator 角色将此方法实现为一个循环,该循环调用 pull-one,直到它耗尽。

方法 skip-one§

method skip-one(Iterator:D: $target --> Mu)

应跳过生成一个值。如果跳过成功,则返回值应为真值,如果没有任何要跳过的值,则返回值应为假值

my $i = <a b>.iterator;
say $i.skip-onesay $i.pull-onesay $i.skip-one
# OUTPUT: «1␤b␤0␤»

Iterator 角色将此方法实现为调用 pull-one,并返回获得的值是否不是 IterationEnd

方法 skip-at-least§

method skip-at-least(Iterator:D: $targetint $to-skip --> Mu)

应跳过生成 $to-skip 个值。如果跳过成功,则返回值应为真值,如果要跳过的值不足,则返回值应为假值

my $i = <a b c>.iterator;
say $i.skip-at-least(2); say $i.pull-onesay $i.skip-at-least(20);
# OUTPUT: «1␤c␤0␤»

Iterator 角色将此方法实现为一个循环,该循环调用 skip-one,并返回它是否返回了足够次数的真值。

方法 skip-at-least-pull-one§

method skip-at-least-pull-one(Iterator:D: $targetint $to-skip --> Mu)

应跳过生成 $to-skip 个值,如果迭代器仍未耗尽,则生成并返回下一个值。如果迭代器在任何时候都已耗尽,则应返回 IterationEnd

my $i = <a b c>.iterator;
say $i.skip-at-least-pull-one(2);
say $i.skip-at-least-pull-one(20=:= IterationEnd;
# OUTPUT: «c␤True␤»

Iterator 角色将此方法实现为调用 skip-at-least,然后在尚未耗尽的情况下调用 pull-one

预测迭代器§

如果 Iterator 可以知道它在不实际生成它们的情况下还可以生成多少个值,请参阅 PredictiveIterator 角色。

类型图§

Iterator 的类型关系
raku-type-graph Iterator Iterator PredictiveIterator PredictiveIterator PredictiveIterator->Iterator

展开上面的图表