http://qs321.pair.com?node_id=544134

TedYoung has asked for the wisdom of the Perl Monks concerning the following question:

So, I finally bought a copy of HOP. In it, the author uses anonymous subs as iterators. The details of this aren't too important for this discussion. He then introduces a function called igrep, which applies a filter to an iterator, much like grep, but one that is evaluated lazily, in the tradition of an iterator.

So, here is an example implementation:

# Syntax sugar sub Iterator (&) { $_[0] } # Takes a sub followed by an iterator sub igrep (&$) { my ($check, $iterator) = @_; return Iterator { # While the source iterator is not exhausted while (defined( local $_ = $iterator->() )) { return $_ if $check->(); } return undef; # we are exhausted } }

The author later introduces some examples of iterators that return lists on each invocation, instead of scalars. To account for this, he starts using a hypothetical igrep_l function that works with lists instead of a scalar. Not hard to implement, but I feel it gets ugly to have different functions for different contexts, especially iterators whose return value is dependant on context.

So, finally the question: how can this igrep function effectively propagate the calling context to the iterator? Here is something that I cobbled together. I was wondering if anyone had any simpler ideas. Note: the sub passed to igrep will have @_ as the param (in a scalar context) or params (in a list context) just retuned from the iterator. $_ is aliased to the first element returned by the iterator for the simple cases where an iterator is always called in a scalar context, or always returns a scalar.

#!perl -l sub Iterator (&) { $_[0] } sub igrep (&$) { my ($check, $iterator) = @_; sub { local *_; my @data; my $wantarray = wantarray; while(@data = ($wantarray ? $iterator->() : scalar $iterator-> +())) { *_ = \ $data[0]; return $wantarray ? @data : $data[0] if $check->(@data); } return undef; } }

So, it seems like there is a lot of checks to wantarray here. An alternative would be to have a giant if (wantarray) dividing the sub into two implementations.

BTW, while reading this book made me think to ask it, I have actually had this problem come up in other domains. I can't remember where I have done before, but I do remember using a similar strategy.

Some other notes: I have intentionally not used strict or warning for the sake of brevity and clarity in these examples. Also, for the same reasons, I am not worried about the void context.

Well, I hope this question has interested more people than it has bored. :-)

Ted Young

($$<<$$=>$$<=>$$<=$$>>$$) always returns 1. :-)

Replies are listed 'Best First'.
Re: Preserving Calling Context
by ysth (Canon) on Apr 18, 2006 at 19:31 UTC
    In it, the author ...
    The author ...
    Seems odd to refer to Mark Jason Dominus so anonymously.

    I too have seen this problem in other domains; whether you go with one large if or conditionals scattered through the code really depends on how hard to read the latter would be or how prone to the problems that result from code duplication the former would be.

Re: Preserving Calling Context
by dragonchild (Archbishop) on Apr 18, 2006 at 18:38 UTC
    So, it seems like there is a lot of checks to wantarray here. An alternative would be to have a giant if (wantarray) dividing the sub into two implementations.

    Sounds like a good plan to me.


    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Preserving Calling Context
by Argel (Prior) on Apr 18, 2006 at 22:56 UTC
    So, it seems like there is a lot of checks to wantarray here. An alternative would be to have a giant if (wantarray) dividing the sub into two implementations.

    Or you could create a subroutine for each case and then create a wrapper subroutine to determine which one to call.