Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing iterable classes with the Iterable and Iterator roles

Tags:

iterator

raku

Suppose we have the following class composing the role 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
    }
}

I could assign the object to a Positional variable during object construction and iterate over that variable:

my @words = Word-Char.new: words => <the sky is blue>;
.say for @words;

OUTPUT:

(the => 3)
(sky => 3)
(is  => 2)
(blue => 4)

However, what if the object is being passed around? How do I make sure it's still iterable?:

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

OUTPUT:

Word-Char.new(words => ["the", "sky", "is", "blue"])

Goal:

By using Iterable, Iterator or both, I would like, if possible, to be able to iterate over an instance object of the class implementing these roles anywhere. Right now I know that by assigning the instance object during the object construction to a Positional variable, I can get the iterable items the class provide but this isn't what I want. Instead I want to pass the object itself and iterate over it wherever/whenever I deem it necessary.

like image 672
Luis F. Uceta Avatar asked Jul 20 '19 03:07

Luis F. Uceta


1 Answers

When dealing with scalar values that do the iterator role, the simplest way to accomplish what you are attempting is to tell perl6 your scalar value is iterable. You can do that by postfixing it with []. Your example then looks like this:

my $w = Word-Char.new: words => <the sky is blue>;
.say for $w[]

Another thing....

Your iteration code has a bug in that it doesn't reset itself before returning IterationEnd. A quick fix looks like the following:

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

However, this means that you have to keep all of the iteration logic (and its attributes) with the main class. Another, way would be to use an anonymous class, instead of using 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;
    } 
}

The advantage of the above is that you can keep your iteration logic cleaner and isolated from the rest of the object. You also don't need to worry about resetting state.

like image 144
Xliff Avatar answered Oct 04 '22 10:10

Xliff