使用 Iterable 和 Iterator 角色实现可迭代 类
Implementing iterable classes with the Iterable and Iterator roles
假设我们有以下class组成角色Iterable
:
class Word-Char does Iterable {
has @.words;
method !pairize($item) {
return $item => $item.chars;
}
method iterator( Word-Char:D: ) {
@!words.map({self!pairize($_)}).rotor(1).iterator
}
}
我可以在对象构造期间将对象分配给 Positional
变量并迭代该变量:
my @words = Word-Char.new: words => <the sky is blue>;
.say for @words;
输出:
(the => 3)
(sky => 3)
(is => 2)
(blue => 4)
但是,如果对象正在传递怎么办?我如何确保它仍然是 iterable?:
my $w = Word-Char.new: words => <the sky is blue>;
sub f( $w ) {
.say for $w
}
f($w);
输出:
Word-Char.new(words => ["the", "sky", "is", "blue"])
目标:
通过使用 Iterable
、Iterator
或两者,如果可能的话,我希望能够在任何地方迭代 class 的实例对象来实现这些角色。现在我知道,通过在对象构造期间将实例对象分配给 Positional
变量,我可以获得 class 提供的 iterable 项,但这不是不是我想要的。相反,我想传递对象本身并对其进行迭代 wherever/whenever 我认为这是必要的。
好的,不清楚你想在这里实现什么,但让我们试一试。
第二个示例中的主要问题是您已将位置(使用 w
)更改为标量。只需再次使用 @w
即可设置
my @w = Word-Char.new: words => <the sky is blue>;
sub f( @w ) {
.say for @w
}
f(@w);
这将以完全相同的方式工作,因为@w 仍然是位置的,因此是可迭代的。当您调用 $w
时,标量只是 returns 它的唯一项,即对象,这就是打印的内容。如果您想在此对象上使用标量印记 并且 也对其进行迭代,您还需要将其设为 Iterator
。
在#perl6 上,jnthn provided several approaches。不过,他们中的一些人并不像我期望的那样行事。
我更新了 class 如下
:
class Word-Char does Iterable does Iterator {
has @.words;
has Int $!index = 0;
method !pairize($item) {
return $item => $item.chars;
}
method iterator() {self}
method pull-one( --> Mu ) {
if $!index < @!words.elems {
my $item = @!words[$!index];
$!index += 1;
return self!pairize($item);
}
else {
return IterationEnd;
}
}
}
1。将对象绑定到 Positional
# Binding to a Positional
my @w01 := Word-Char.new: words => <the sky is blue>;
这会产生以下错误:
Type check failed in binding; expected Positional but got Word-Char...
2。在迭代点使用|
my $w = Word-Char.new: words => <the sky is blue>;
for |$w {
.say
}
=begin comment
Word-Char.new(words => ["the", "sky", "is", "blue"])
=end comment
|
对似乎保持其标量性质的对象没有影响,因此 for
不会对其进行迭代。
3。使用无符号变量
my \w = Word-Char.new: words => <the sky is blue>;
for w {
.say
}
=begin comment
he => 3
sky => 3
is => 2
blue => 4
=end comment
到目前为止,这是最干净的方法,符合我的预期。
4。添加一个 returns 可迭代的方法,而不是使 class 可迭代。
事实上,这是我的第一种方法,但我没有发现它太过 p6y。在任何情况下,为了让它工作,我们需要更新我们的 class 并添加一个 returns 可迭代的方法。我选择的方法名称是 LOOP-OVER
只是为了让它从其他所有内容中脱颖而出。
class Word-Char {
has @.words;
method !pairize($item) {
return $item => $item.chars;
}
method LOOP-OVER {
gather for @!words -> $word {
take self!pairize($word)
}
}
}
my $w = Word-Char.new: words => <the sky is blue>;
for $w.LOOP-OVER {
.say
}
=begin comment
he => 3
sky => 3
is => 2
blue => 4
=end comment
但是如果我们依赖多个 classes 的迭代行为呢?我们如何确保他们实施相同的方法?最直接的方法
是组成一个角色(例如,Iterationable
),在这种情况下,它实现存根 LOOP-OVER
方法。
role Iterationable {
method LOOP-OVER { ... }
}
class Word-Char does Iterationable {
has @.words;
method !pairize($item) {
return $item => $item.chars;
}
method LOOP-OVER {
gather for @!words -> $word {
take self!pairize($word)
}
}
}
class Names does Iterationable {
has @.names;
method LOOP-OVER {
gather for @!names -> $name {
take $name.split(/\s+/)».tc.join(' ')
}
}
}
class NotIterable {
has @.items
}
my @objs =
Word-Char.new(words => <the sky is blue>),
Names.new(names => ['Jose arat', 'elva delorean', 'alphonse romer']),
NotIterable.new(items => [5, 'five', 'cinco', 'cinq'])
;
for @objs -> $obj {
if $obj.can('LOOP-OVER') {
put "» From {$obj.^name}: ";
for $obj.LOOP-OVER {
.say
}
}
else {
put "» From {$obj.^name}: Cannot iterate over it";
}
}
=begin comment
» From Word-Char:
the => 3
sky => 3
is => 2
blue => 4
» From Names:
Jose Arat
Elva Delorean
Alphonse Romer
» From NotIterable: Cannot iterate over it
=end comment
如 jnthn 所述,使用何种方法(至少从工作方法)几乎不取决于手头的问题。
在处理充当迭代器角色的标量值时,完成您尝试的最简单方法是告诉 perl6 您的标量值是可迭代的。您可以通过在其后缀 []
来做到这一点。你的例子看起来像这样:
my $w = Word-Char.new: words => <the sky is blue>;
.say for $w[]
另一件事....
您的迭代代码有一个错误,它在返回 IterationEnd
之前不会自行重置。快速修复如下所示:
class Word-Char does Iterable does Iterator {
has @.words;
has Int $!index = 0;
method !pairize($item) {
return $item => $item.chars;
}
method iterator() {self}
method pull-one( --> Mu ) {
if $!index < @!words.elems {
my $item = @!words[$!index];
$!index += 1;
return self!pairize($item);
}
else {
$!index = 0;
return IterationEnd;
}
}
}
然而,这意味着您必须将所有迭代逻辑(及其属性)保留在主 class 中。另一种方法是使用匿名 class,而不是使用 self
:
class Word-Char does Iterable {
has @.words;
method !pairize($item) {
return $item => $item.chars;
}
method iterator() {
my @words = @!words;
class :: does Iterator {
has $.index is rw = 0;
method pull-one {
return IterationEnd if $!index >= @words.elems;
@words[$!index++];
}
}.new;
}
}
上述的优点是您可以使迭代逻辑更清晰并与对象的其余部分隔离。您也不必担心重置状态。
另一个(有点乱)解决方案是:
class Word-Char does Iterator {
has @.words;
has Int $.index is rw = 0;
method pull-one() {
LEAVE { $!index++ }
return $!index < @!words.elems
?? (@!words[$!index] => @!words[$!index].chars)
!! IterationEnd;
}
}
my $w = Word-Char.new: words => <the sky is blue>;
my $seq = Seq.new($w).cache;
sub f( $w ) {
.say for $w[]
}
f($seq);
$w.index = 0;
f($seq);
假设我们有以下class组成角色Iterable
:
class Word-Char does Iterable {
has @.words;
method !pairize($item) {
return $item => $item.chars;
}
method iterator( Word-Char:D: ) {
@!words.map({self!pairize($_)}).rotor(1).iterator
}
}
我可以在对象构造期间将对象分配给 Positional
变量并迭代该变量:
my @words = Word-Char.new: words => <the sky is blue>;
.say for @words;
输出:
(the => 3)
(sky => 3)
(is => 2)
(blue => 4)
但是,如果对象正在传递怎么办?我如何确保它仍然是 iterable?:
my $w = Word-Char.new: words => <the sky is blue>;
sub f( $w ) {
.say for $w
}
f($w);
输出:
Word-Char.new(words => ["the", "sky", "is", "blue"])
目标:
通过使用 Iterable
、Iterator
或两者,如果可能的话,我希望能够在任何地方迭代 class 的实例对象来实现这些角色。现在我知道,通过在对象构造期间将实例对象分配给 Positional
变量,我可以获得 class 提供的 iterable 项,但这不是不是我想要的。相反,我想传递对象本身并对其进行迭代 wherever/whenever 我认为这是必要的。
好的,不清楚你想在这里实现什么,但让我们试一试。
第二个示例中的主要问题是您已将位置(使用 w
)更改为标量。只需再次使用 @w
即可设置
my @w = Word-Char.new: words => <the sky is blue>;
sub f( @w ) {
.say for @w
}
f(@w);
这将以完全相同的方式工作,因为@w 仍然是位置的,因此是可迭代的。当您调用 $w
时,标量只是 returns 它的唯一项,即对象,这就是打印的内容。如果您想在此对象上使用标量印记 并且 也对其进行迭代,您还需要将其设为 Iterator
。
在#perl6 上,jnthn provided several approaches。不过,他们中的一些人并不像我期望的那样行事。
我更新了 class 如下
class Word-Char does Iterable does Iterator {
has @.words;
has Int $!index = 0;
method !pairize($item) {
return $item => $item.chars;
}
method iterator() {self}
method pull-one( --> Mu ) {
if $!index < @!words.elems {
my $item = @!words[$!index];
$!index += 1;
return self!pairize($item);
}
else {
return IterationEnd;
}
}
}
1。将对象绑定到 Positional
# Binding to a Positional
my @w01 := Word-Char.new: words => <the sky is blue>;
这会产生以下错误:
Type check failed in binding; expected Positional but got Word-Char...
2。在迭代点使用|
my $w = Word-Char.new: words => <the sky is blue>;
for |$w {
.say
}
=begin comment
Word-Char.new(words => ["the", "sky", "is", "blue"])
=end comment
|
对似乎保持其标量性质的对象没有影响,因此 for
不会对其进行迭代。
3。使用无符号变量
my \w = Word-Char.new: words => <the sky is blue>;
for w {
.say
}
=begin comment
he => 3
sky => 3
is => 2
blue => 4
=end comment
到目前为止,这是最干净的方法,符合我的预期。
4。添加一个 returns 可迭代的方法,而不是使 class 可迭代。
事实上,这是我的第一种方法,但我没有发现它太过 p6y。在任何情况下,为了让它工作,我们需要更新我们的 class 并添加一个 returns 可迭代的方法。我选择的方法名称是 LOOP-OVER
只是为了让它从其他所有内容中脱颖而出。
class Word-Char {
has @.words;
method !pairize($item) {
return $item => $item.chars;
}
method LOOP-OVER {
gather for @!words -> $word {
take self!pairize($word)
}
}
}
my $w = Word-Char.new: words => <the sky is blue>;
for $w.LOOP-OVER {
.say
}
=begin comment
he => 3
sky => 3
is => 2
blue => 4
=end comment
但是如果我们依赖多个 classes 的迭代行为呢?我们如何确保他们实施相同的方法?最直接的方法
是组成一个角色(例如,Iterationable
),在这种情况下,它实现存根 LOOP-OVER
方法。
role Iterationable {
method LOOP-OVER { ... }
}
class Word-Char does Iterationable {
has @.words;
method !pairize($item) {
return $item => $item.chars;
}
method LOOP-OVER {
gather for @!words -> $word {
take self!pairize($word)
}
}
}
class Names does Iterationable {
has @.names;
method LOOP-OVER {
gather for @!names -> $name {
take $name.split(/\s+/)».tc.join(' ')
}
}
}
class NotIterable {
has @.items
}
my @objs =
Word-Char.new(words => <the sky is blue>),
Names.new(names => ['Jose arat', 'elva delorean', 'alphonse romer']),
NotIterable.new(items => [5, 'five', 'cinco', 'cinq'])
;
for @objs -> $obj {
if $obj.can('LOOP-OVER') {
put "» From {$obj.^name}: ";
for $obj.LOOP-OVER {
.say
}
}
else {
put "» From {$obj.^name}: Cannot iterate over it";
}
}
=begin comment
» From Word-Char:
the => 3
sky => 3
is => 2
blue => 4
» From Names:
Jose Arat
Elva Delorean
Alphonse Romer
» From NotIterable: Cannot iterate over it
=end comment
如 jnthn 所述,使用何种方法(至少从工作方法)几乎不取决于手头的问题。
在处理充当迭代器角色的标量值时,完成您尝试的最简单方法是告诉 perl6 您的标量值是可迭代的。您可以通过在其后缀 []
来做到这一点。你的例子看起来像这样:
my $w = Word-Char.new: words => <the sky is blue>;
.say for $w[]
另一件事....
您的迭代代码有一个错误,它在返回 IterationEnd
之前不会自行重置。快速修复如下所示:
class Word-Char does Iterable does Iterator {
has @.words;
has Int $!index = 0;
method !pairize($item) {
return $item => $item.chars;
}
method iterator() {self}
method pull-one( --> Mu ) {
if $!index < @!words.elems {
my $item = @!words[$!index];
$!index += 1;
return self!pairize($item);
}
else {
$!index = 0;
return IterationEnd;
}
}
}
然而,这意味着您必须将所有迭代逻辑(及其属性)保留在主 class 中。另一种方法是使用匿名 class,而不是使用 self
:
class Word-Char does Iterable {
has @.words;
method !pairize($item) {
return $item => $item.chars;
}
method iterator() {
my @words = @!words;
class :: does Iterator {
has $.index is rw = 0;
method pull-one {
return IterationEnd if $!index >= @words.elems;
@words[$!index++];
}
}.new;
}
}
上述的优点是您可以使迭代逻辑更清晰并与对象的其余部分隔离。您也不必担心重置状态。
另一个(有点乱)解决方案是:
class Word-Char does Iterator {
has @.words;
has Int $.index is rw = 0;
method pull-one() {
LEAVE { $!index++ }
return $!index < @!words.elems
?? (@!words[$!index] => @!words[$!index].chars)
!! IterationEnd;
}
}
my $w = Word-Char.new: words => <the sky is blue>;
my $seq = Seq.new($w).cache;
sub f( $w ) {
.say for $w[]
}
f($seq);
$w.index = 0;
f($seq);