11

I'm trying to make an iterator, then build a sequence from it, but it doesn't act the way I think it should. What's up?

Here's my basic class:

class Foo {
    has $.x = 0;
    has $.max = 3;

    method val() {
        (++$!x > $!max) ?? () !! ($!x, "string $!x")
    }
}

my $foo = Foo.new;
say $foo.val.perl for ^4;

# (1, "string 1")
# (2, "string 2")
# (3, "string 3")
# ()

It just iterates until max, then returns (), works the way I think it should.

Then I build an Iterator from that, just a single pull-one() method.

class Foo-Iterator does Iterator {
    has Foo $.foo;

    method pull-one() {
        $!foo.val || IterationEnd
    }
}

my $iter = Foo-Iterator.new(foo => Foo.new);
$iter.pull-one.perl.say for ^4;

# (1, "string 1")
# (2, "string 2")
# (3, "string 3")
# IterationEnd

It still acts the way I expect it to.

If I access it with a Seq, it still works fine:

.perl.say for Seq.new: Foo-Iterator.new(foo => Foo.new);
# (1, "string 1")
# (2, "string 2")
# (3, "string 3")

That is still what I expect to see, the same thing the Iterator returned.

Finally, I store the Seq in an @ variable and print the results of that:

my @seq = Seq.new: Foo-Iterator.new(foo => Foo.new);
.perl.say for @seq;
# $(4, "string 1")
# $(4, "string 2")
# $(4, "string 3")

What's up with that? It is seems to be using the later value of the variable rather than the value it had at the time of the pull-one call() (which the string forces to be a value). Does a Seq return it as a container rather than a value? Is this laziness in action, where it doesn't pull until requested so it gets a later value?

If I make val() return +$!x instead of returning $!x, it seems to grab the value and give me what I want, I'm just trying to understand the behavior I see.

2
  • 2
    This is Rakudo version 2018.11 built on MoarVM version 2018.11 implementing Perl 6.d. Commented Jan 16, 2019 at 1:36
  • 2
    It also works the way I think it should if I use a $ variable to store the Seq, then iterate with for @$seq. Commented Jan 16, 2019 at 1:47

1 Answer 1

8

I made precisely 4 changes to Foo: does Iterator, pull-one, IterationEnd, and I decont %!x with <>.

Your method was passing the container for $!x when you really want to pass the value inside of the container, so it needed $!x<>.
The reason you didn't notice it with the rest of the code is that the array assignment was the only one that was eager.

I did the rest of the changes because it already has state, and it only works once. Which is exactly how an Iterator should work. With your code it makes zero sense to add another object for an Iterator that basically only renames a method.

class Foo does Iterator {
    #     ^-----------^
    has $.x = 0;
    has $.max = 3;

    method pull-one() {
        #  ^------^

        (++$!x > $!max) ?? IterationEnd !! ($!x<>, "string $!x")
        #                  ^----------^        ^^
    }
}

Now to use it

my $seq = Seq.new( Foo.new );

for $seq<> { .say }
# (1 string 1)
# (2 string 2)
# (3 string 3)

my @seq = Seq.new( Foo.new );
for @seq { .say }
# (1 string 1)
# (2 string 2)
# (3 string 3)

Assuming that your example is entirely too simple, and there is a good reason for having a separate Iterator, why do you have a mutating val method?

class Foo-Iterator {…}

class Foo does Iterable {
    has $.max = 3;

    method val ( $index ) {
        ($index > $!max) ?? () !! ($index, "string $index")
    }

    method iterator (){
        # pass `self` for method calls or such
        # (could pass internal data additionally/instead)
        Foo-Iterator.new( :foo(self) )
    }
}

class Foo-Iterator does Iterator {
    # the mutating value
    has $!x = 0;

    # make it public only so we don't have
    # to mess with `new` or `BUILD`
    has $.foo is required;

    # only have the mutating logic in this object
    method pull-one() {
        $!foo.val( ++$!x ) || IterationEnd
    }
}

Now to use it.

# only one Foo object needed
my $foo = Foo.new;

# for Seq.new($foo.iterator) { .say }
for $foo<> { .say }
# (1 string 1)
# (2 string 2)
# (3 string 3)

for $foo<> { .say }
# (1 string 1)
# (2 string 2)
# (3 string 3)

my $iter-a = $foo.iterator;
my $iter-b = $foo.iterator;

say $iter-a.pull-one;
# (1 string 1)
say $iter-a.pull-one;
# (2 string 2)
say $iter-b.pull-one; # notice that $iter-b isn't tied to $iter-a
# (1 string 1)

my @seq = $foo<>;
for @seq { .say }
# (1 string 1)
# (2 string 2)
# (3 string 3)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.