【问题标题】:Implementing iterable classes with the Iterable and Iterator roles使用 Iterable 和 Iterator 角色实现可迭代类
【发布时间】:2019-07-20 03:25:52
【问题描述】:

假设我们有以下类组成角色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)

但是,如果对象被传递怎么办?如何确保它仍然可迭代?:

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"])

目标

通过使用IterableIterator 或同时使用两者,如果可能的话,我希望能够在任何地方迭代实现这些角色的类的实例对象。现在我知道通过在对象构造期间将实例对象分配给Positional 变量,我可以获得类提供的iterable 项,但这不是我想要的。相反,我想传递对象本身并在我认为有必要的任何地方/任何时候对其进行迭代。

【问题讨论】:

标签: iterator raku


【解决方案1】:

在处理扮演迭代器角色的标量值时,完成您尝试的最简单的方法是告诉 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;
        }
    }
}

但是,这意味着您必须将所有迭代逻辑(及其属性)保留在主类中。另一种方法是使用匿名类,而不是使用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;
    } 
}

上面的优点是你可以保持你的迭代逻辑更清晰,并与对象的其余部分隔离。您也不必担心重置状态。

【讨论】:

  • 欢迎来到 SO/SE Xliff!这是我第一次看到匿名类真正体面的用例。
  • 使用匿名类的解决方案无法编译。 .pull-one() 方法中的错误是 Attribute $!index not declared in class Word-Char
  • @FernandoSantagata - 确认!你是对的。我已经更新了代码。
【解决方案2】:

好的,不清楚你想在这里实现什么,但让我们试一试。 第二个示例中的主要问题是您使用标量更改了位置(使用w)。只需再次使用@w 即可完成

my @w = Word-Char.new: words => <the sky is blue>;
sub f( @w ) {
    .say for @w
}
f(@w);

这将以完全相同的方式工作,因为@w 仍然是位置性的,因此是可迭代的。当您调用$w 时,标量只返回其唯一的项目,即对象,这就是打印的内容。如果你想在这个对象上使用标量印记并且也迭代它,你还需要将它设为Iterator

【讨论】:

  • 我可能误解了迭代的含义。我以为我正在将该属性赋予该类,因此它的任何实例都可以在任何地方迭代,而不仅仅是在对象构造期间。通过在对象构造期间将对象分配给Positional,我可以获得对象的可迭代项,但我希望能够传递对象。这就是为什么我问 但是,如果对象被传递怎么办?如何确保它仍然是可迭代的?.
  • 我想过传递对象,然后将其分配给 Positional 以获取其可迭代项,但这似乎不起作用。就像这样sub f($w) { my @items = $w; .say for @items; }。我会尝试使用Iterator 角色并报告。
  • @uzlxxxx 存在 Iterable 和 Iterator 的事实让人有点困惑; Iterable 还有iterator 方法的事实。但是文档中的示例应该很清楚。使它可迭代的原因是它是一个位置。如果它不再是 Positional,它将不会获得 Iterable 角色。
  • 你是对的。我认为这可以在文档中进一步改进。 Iterable 之所以成为Iterable,是因为它是Positional 更准确的说法是 Iterable 是因为它被分配给@ 987654335@。 正如我所见,在将对象分配给Positional 之后,我们失去了对对象本身的控制,而是对它的可迭代元素的控制。感谢您的澄清。
  • @uzlxxxx 是的,有点,但它是可迭代的位置,而不是对象......
【解决方案3】:

在 #perl6 上,jnthn provided several approaches。他们中的一些人并没有像我期望的那样行事。

我按照以下方式更新了课程 jjmerelo's suggestion:

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。将对象绑定到位置

# 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。与其让类可迭代,不如添加一个返回可迭代内容的方法。

事实上,这是我的第一个方法,但我没有发现它太p6y。无论如何,要让它工作,我们需要更新我们的类并添加一个返回可迭代内容的方法。我选择的方法名称是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

但是,如果我们依赖多个迭代行为的类怎么办?我们如何确保它们实现相同的方法?最直接的方法 在这种情况下,是组成一个角色(例如,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 所述,使用哪种方法(至少来自工作方法)几乎不取决于手头的问题。

【讨论】:

  • Re #1,让一个类实现 Positional 实际上相当容易,如果出于所有意图和目的您想要这样使用它,我建议您这样做。您只需必须实现 3 个方法:elemsAT-POSEXISTS-POS。因为你有一个内部数组,你可以把事情委托给它:method elems { @words.elems }; method AT-POS($i) { @words[$i] }; method EXISTS-POS($i) { @words[$i]:exists },它会让你的用户生活更轻松(实际上它可能会让for |@words工作,即使你永远不会让它成为Iterable,但我没有测试过)
【解决方案4】:

另一个(有点混乱的)解决方案是:

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);

【讨论】:

    猜你喜欢
    • 2020-01-24
    • 2011-08-15
    • 1970-01-01
    • 2013-03-03
    • 2019-04-26
    • 2019-03-24
    • 2015-12-13
    • 2013-10-05
    • 2015-09-09
    相关资源
    最近更新 更多