role PredictiveIterator
Iterators that can predict number of values
A PredictiveIterator
is a special kind of Iterator that can know how many values it will (still) generate without actually needing to generate those values.
The main addition to the API of the Iterator role, is the count-only
method, which should return the number of values the Iterator is still able to generate.
The other addition is the bool-only
method, that should return a Bool
indicating whether the Iterator is still capable of producing values (aka, is not exhausted yet). By default, this is the Booleanification of the result of the call to the count-only
method.
Methods
method count-only
Defined as:
method count-only(--> Int)
It is expected to return the number of values the iterator can still produce without actually producing them. The returned number must adjust itself for items already pulled, so that the method can be called on a partially consumed Iterator
.
It will be used in situations where only the number of values of an iterator is needed, e.g. when the .elems
method is called.
Important: it's expected the Iterator
s that implement this method can return that number without producing any values. In other words, it's expected the user of the class will be able to still pull-one after calling this method, and eventually receive as many values as the return value of this method indicated.
method bool-only
Defaults to the Booleanification of the result of calling the count-only
method. If it is possible to have a faster way of finding out whether the iterator is capable of producing any value, it should be implemented.
Defined as:
method bool-only(--> Bool)
Type Graph
Routines supplied by role Iterator
PredictiveIterator does role Iterator, which provides the following routines:
(Iterator) method pull-one
Defined as:
method pull-one(Iterator: --> Mu)
This method stub ensures that classes implementing the Iterator
role provide a method named pull-one
.
The pull-one
method is supposed to produce and return the next value if possible, or return the sentinel value IterationEnd
if no more values could be produced.
my = (1 .. 3).iterator;say .pull-one; # OUTPUT: «1»say .pull-one; # OUTPUT: «2»say .pull-one; # OUTPUT: «3»say .pull-one.perl; # OUTPUT: «IterationEnd»
As a more illustrative example of its use, here is a count down iterator along with a simplistic subroutine re-implementation of the for
loop.
# works the same as (10 ... 1, 'lift off')does Iteratorsub for( Iterable , --> Nil )for( Seq.new(CountDown.new), ); # OUTPUT: «10987654321lift off»
It would be more idiomatic to use while
or until
, and a sigilless variable.
until IterationEnd =:= (my \pulled = .pull-one)
(Iterator) method push-exactly
Defined as:
method push-exactly(Iterator: , int --> Mu)
Should produce $count
elements, and for each of them, call $target.push($value)
.
If fewer than $count
elements are available from the iterator, it should return the sentinel value IterationEnd
. Otherwise it should return $count
.
my ;say (1 .. ∞).iterator.push-exactly(, 3); # OUTPUT: «3»say ; # OUTPUT: «[1 2 3]»
The Iterator role implements this method in terms of pull-one
. In general, this is a method that is not intended to be called directly from the end user who, instead, should implement it in classes that mix the iterator role. For instance, this class implements that role:
does Iterable does Iterator;my := DNA.new("AAGCCT");for -> , , ; # Does not enter the loopmy := DNA.new("CAGCGGAAGCCT");for -> ,
This code, which groups DNA chains in triplets (usually called codons) returns those codons when requested in a loop; if too many are requested, like in the first case for $b -> $a, $b, $c
, it simply does not enter the loop since push-exactly
will return IterationEnd
since it is not able to serve the request for exactly 3 codons. In the second case, however, it requests exactly two codons in each iteration of the loop; push-exactly
is being called with the number of loop variables as the $count
variable.
(Iterator) method push-at-least
Defined as:
method push-at-least(Iterator: , int --> Mu)
Should produce at least $count
elements, and for each of them, call $target.push($value)
.
If fewer than $count
elements are available from the iterator, it should return the sentinel value IterationEnd
. Otherwise it should return $count
.
Iterators with side effects should produce exactly $count
elements; iterators without side effects (such as Range iterators) can produce more elements to achieve better performance.
my ;say (1 .. ∞).iterator.push-at-least(, 10); # OUTPUT: «10»say ; # OUTPUT: «[1 2 3 4 5 6 7 8 9 10]»
The Iterator role implements this method in terms of pull-one
. In general, it is also not intended to be called directly as in the example above. It can be implemented, if unhappy with this default implementation, by those using this role. See the documentation for push-exactly
for an example implementation.
(Iterator) method push-all
Defined as:
method push-all(Iterator: )
Should produce all elements from the iterator and push them to $target
.
my ;say (1 .. 1000).iterator.push-all(); # All 1000 values are pushed
The Iterator role implements this method in terms of push-at-least
. As in the case of the other push-*
methods, it is mainly intended for developers implementing this role. push-all
is called when assigning an object with this role to an array, for instance, like in this example:
does Iterable does Iterator;my := DNA.new("AAGCCT");my = ;say ; # OUTPUT: «[(A A G) (C C T)]»
The push-all
method implemented pushes to the target iterator in lists of three aminoacid representations; this is called under the covers when we assign $b
to @dna-array
.
(Iterator) method push-until-lazy
Defined as:
method push-until-lazy(Iterator: --> Mu)
Should produce values until it considers itself to be lazy, and push them onto $target
.
The Iterator role implements this method as a no-op if is-lazy
returns a True value, or as a synonym of push-all
if not.
This matters mostly for iterators that have other iterators embedded, some of which might be lazy, while others aren't.
(Iterator) method is-lazy
Defined as:
method is-lazy(Iterator: --> Bool)
Should return True
for iterators that consider themselves lazy, and False
otherwise.
Built-in operations that know that they can produce infinitely many values return True
here, for example (1..6).roll(*)
.
say (1 .. 100).is-lazy; # OUTPUT: «False»say (1 .. ∞).is-lazy; # OUTPUT: «True»
The Iterator role implements this method returning False
, indicating a non-lazy iterator.
(Iterator) method sink-all
Defined as:
method sink-all(Iterator: --> IterationEnd)
Should exhaust the iterator purely for the side-effects of producing the values, without actually saving them in any way. Should always return IterationEnd
. If there are no side-effects associated with producing a value, then it can be implemented by a consuming class to be a virtual no-op.
say (1 .. 1000).iterator.sink-all; # OUTPUT: «IterationEnd»
The Iterator role implements this method as a loop that calls pull-one
until it is exhausted.
(Iterator) method skip-one
Defined as:
method skip-one(Iterator: --> Mu)
Should skip producing one value. The return value should be truthy if the skip was successful and falsy if there were no values to be skipped:
my = <a b>.iterator;say .skip-one; say .pull-one; say .skip-one# OUTPUT: «1b0»
The Iterator role implements this method as a call pull-one
and returning whether the value obtained was not IterationEnd
.
(Iterator) method skip-at-least
Defined as:
method skip-at-least(Iterator: , int --> Mu)
Should skip producing $to-skip
values. The return value should be truthy if the skip was successful and falsy if there were not enough values to be skipped:
my = <a b c>.iterator;say .skip-at-least(2); say .pull-one; say .skip-at-least(20);# OUTPUT: «1c0»
The Iterator role implements this method as a loop calling skip-one
and returning whether it returned a truthy value sufficient number of times.
(Iterator) method skip-at-least-pull-one
Defined as:
method skip-at-least-pull-one(Iterator: , int --> Mu)
Should skip producing $to-skip
values and if the iterator is still not exhausted, produce and return the next value. Should return IterationEnd
if the iterator got exhausted at any point:
my = <a b c>.iterator;say .skip-at-least-pull-one(2);say .skip-at-least-pull-one(20) =:= IterationEnd;# OUTPUT: «cTrue»
The Iterator role implements this method as calling skip-at-least
and then calling pull-one
if it was not exhausted yet.