Emulating Generators: Iterators for Lists (Newbie)

  • Thread starter Veli-Pekka Tätilä
  • Start date
V

Veli-Pekka Tätilä

Hi,
I've been asking a number of pretty generic but newbie questions on
achieving various things in Perl. Here's yet another one. I checked the faq
in perldoc but haven't Googled the archives yet:

What would be a good way to do an iterator for a sequence of values to be
returned, also called a generator, in Perl? That is for a sub-routine
returning a list of values in list context, have it return each element of
the list in scalar context followed by undef. For an analogy, you could
think of each in scalar context Vs. the list returned by keys. Iterators
would save a lot of memory and might be faster, too, if you'll need to
allocate huge lists. Not to mention infinite sequences of which you'd like
to iterate to some finite length determined at runtime. I think some Perl
primitives, such as a list of values generated by the range operator for a
foreach loop, are already optimized for memory efficiency. Python has the
Xrange construct, as well.

One choice that came to mind very first would be to have a block around a
sub-routine, and declare the state you need to maintain between sub calls
for generating the next value, in this block. By refering to the outerblock
in the inner, the variables in the outer are kept alive as long as the
program itself, kind of like static variables in C. The trouble I have with
this approach is that the contents remain even after n subroutine cals
eating memory. Could one simply undef the array or hash in question once the
sub hits the end of the list it's generating? I suppose so but havent'
tested it yet.

Another way of which I'm aware would be to use closures. Return a reference
to a hash of references to anonymous sub-routines. If these routines refer
to constructs in the outer sub-routine block, Perl will keep a set of the
values alive for each hash of subs returned. I feel like I don't really
understand how this works, that is is derived from Perl's scoping rules, but
I just know that this is what happens and have used it myself. A slight
niggle I have with the closure approach is that the syntax is a bit querky
and neither looks like a basic method or subroutine calle, because you'll
have to index to a hash and derefrence the value as a sub. By the hway,
could someone explain closures in detail or point me to good docs on the
Web? A tutorial, down-to-Earth approach would be best, I suppose.

The third choice would be to do the same thing as in the second case but as
an object. YOu could create the data in the constructor, have some kind of
iterator method and the object would be destroyed once there are no
references to it as usual. This is not a simpel sub-routine any more,
however.

Now that I started thinking of this, another problem with returning one
value at a time is that you cannot use this kind of iterator transparently
as a list. Even if a sub handling a list would need to look at only one or
two subsequent list items at once, you'll have to feed it the whole list in
the beginning of the sub-routine call, though you could pass a reference,
however. As to example functions, map needs only one list item at a time
while reduce needs two. Again, are there any good work-arounds for getting
around this limitation of iterators?

I recall reading somewhere that Perl 6 is going to have lazily expanding
lists which might be the answer. But howabout Perl 5, then? Of course you
could feed the sub-routine an iterator in stead but many subs, especially
the built-ins, like to deal with long lists of values.

Any help appreciated as usual. Feel free to point me to earlier threads as
well, though please give as direct links as you can.
 
B

Brian McCauley

Veli-Pekka Tätilä said:
Hi,
I've been asking a number of pretty generic but newbie questions on
achieving various things in Perl. Here's yet another one. I checked the faq
in perldoc but haven't Googled the archives yet:

(Why not?)
What would be a good way to do an iterator for a sequence of values to be
returned, also called a generator, in Perl? That is for a sub-routine
returning a list of values in list context, have it return each element of
the list in scalar context followed by undef.

The most direct way would be just do it...

sub make_gen {
my @list = @_;
my $i = 0;
sub {
return @list if wantarray;
$i=0 if $i > @list;
$list[$i++]
}
}
One choice that came to mind very first would be to have a block around a
sub-routine, and declare the state you need to maintain between sub calls
for generating the next value, in this block. By refering to the outerblock
in the inner, the variables in the outer are kept alive as long as the
program itself, kind of like static variables in C.

Yes that is one standard approach.
The trouble I have with
this approach is that the contents remain even after n subroutine cals
eating memory. Could one simply undef the array or hash in question once the
sub hits the end of the list it's generating? I suppose so but havent'
tested it yet.

Nothing wrong with that either.
Another way of which I'm aware would be to use closures. Return a reference
to a hash of references to anonymous sub-routines.

I don't see where the hash comes into this.
If these routines refer
to constructs in the outer sub-routine block, Perl will keep a set of the
values alive for each hash of subs returned. I feel like I don't really
understand how this works, that is is derived from Perl's scoping rules, but
I just know that this is what happens and have used it myself. A slight
niggle I have with the closure approach is that the syntax is a bit querky
and neither looks like a basic method or subroutine calle, because you'll
have to index to a hash and derefrence the value as a sub.

Like, I said, I don't see where the hash comed into this.
By the hway,
could someone explain closures in detail or point me to good docs on the
Web? A tutorial, down-to-Earth approach would be best, I suppose.

Many of the frequent contributors to this newsgroup have tutorial
sites. Try Googling this group.
The third choice would be to do the same thing as in the second case but as
an object. YOu could create the data in the constructor, have some kind of
iterator method and the object would be destroyed once there are no
references to it as usual. This is not a simpel sub-routine any more,
however.

Yes, that is another standard approach. It's also the one I most
commonly opt for.
Now that I started thinking of this, another problem with returning one
value at a time is that you cannot use this kind of iterator transparently
as a list.

Yes, sorry this isn't Haskell. Iterators look like iterators, lists
look like lists (with a very few exceptions like the .. in foreach you
mentioned above).
Even if a sub handling a list would need to look at only one or
two subsequent list items at once, you'll have to feed it the whole list in
the beginning of the sub-routine call, though you could pass a reference,
however. As to example functions, map needs only one list item at a time
while reduce needs two. Again, are there any good work-arounds for getting
around this limitation of iterators?

If something expects an array as opposed to a list and then only shifts
that array then you can pass it a tied array but in all but a very few
cases is this worthwhile. Generally it's better to modify the thing in
question to accept an iterator.
I recall reading somewhere that Perl 6 is going to have lazily expanding
lists which might be the answer

Yes, all your language are belong us! (Even Haskell).
But howabout Perl 5, then? Of course you
could feed the sub-routine an iterator in stead but many subs, especially
the built-ins, like to deal with long lists of values.

AFAIK lazy evaluation has to be deep in the heart of a language.
 
S

Steven N. Hirsch

Veli-Pekka Tätilä said:
What would be a good way to do an iterator for a sequence of values to be
returned, also called a generator, in Perl?

You will probably find Mark-Jason Dominus' book "Higher Order Perl" of
interest.
 
V

Veli-Pekka T?til?

Veli-Pekka T?til? said:
Hi,
I've been asking a number of pretty generic but newbie questions
on achieving various things in Perl. Here's yet another one. I
checked the faq in perldoc but haven't Googled the archives yet:

There is no reason to do either before asking a question. And even
if you did both, there is no reason to tell anyone that you have
done either.
 
U

Uri Guttman

BM> sub make_gen {
BM> my @list = @_;
BM> my $i = 0;
BM> sub {
BM> return @list if wantarray;
BM> $i=0 if $i > @list;
BM> $list[$i++]
BM> }
BM> }

that has several issues that should be addressed. it doesn't return an
empty list if called in array context more than once. there is no
relationship between the iterator in scalar and list context (you can
get some elements in scalar context and a call in list context will
return the entire array, not just what is left). after you count past
the array it will keep counting $i. here is how i would do it:

sub make_gen {

my( @list ) = @_;

return sub {

# this handles both contexts

return unless list ;

# scalar gets and deletes next value

return shift @list unless wantarray ;

# return and delete rest of array in list context

return splice( @list, 0 ) ;
}
}

uri
 
B

Brian McCauley

Uri said:
BM> [ snip really strange iterator generator ]

that has several issues that should be addressed. it doesn't return an
empty list if called in array context more than once. there is no
relationship between the iterator in scalar and list context (you can
get some elements in scalar context and a call in list context will
return the entire array, not just what is left). after you count past
the array it will keep counting $i

Somehow when I read the OP those were the rather odd requirements I
saw. (Full list in the list context and in a scalar context return each
element of the list followed by undef then start again).

Looking again I can see none of these perverse requirements. I must
have been halucinating.
. here is how i would do it:

[ snip exactly what I'd have done if I'd not been halucinating ]

Well, maybe I'd not have made the typo. :)

Although to be honest I might have missed the splice(@list,0) trick.
 
V

Veli-Pekka Tätilä

Brian said:
(Why not?)
Well, here's the short answer. Firstly, frankly speaking out of false
lazyness. Secondly, because Google archives are Web-based and a normal
desktop mail or news app is lightyears ahead of most Web sites as far as
keyboard usability and rendering the UI to speech goes. If there was such an
interface to Google groups, I'd use them a lot more than I do now. The same
applies to Web fora Vs mail and NGs. Thirdly, I've noticed that having to
explaing the problems and possible solutions to others requires you to think
things over. I already discovered some new ways of looking at this whene
writing my original post.
I don't see where the hash comes into this.
Frankly speaking I'm not sure if I do either, <grin>. Most closure examples
I've seen return a ref to a hash of subs for some reason. I thought that
returning a ref or using a hash, in addition to efficiency, might be an
integral requirement to turn subs into closures. As I said I've been told
how closures work at some level but I cannot say I really understand them.
When I started OOP, I had similar feelings on polymorphism for quite some
time.
Yes, sorry this isn't Haskell. Iterators look like iterators, lists
look like lists (with a very few exceptions like the .. in foreach you
mentioned above).
Thanks for pointing out Haskell, I've not gotten into it but a programming
friend of mine, who knows much more than I do, has been talking about it. I
like the functional programming constructs in Perl but having to go all the
way functional doesn't really intrigue me.

Still I cannot help thinking whether the built-ins could be made to consume
less memory if the list operators needing only a few elements, would take
those elements progressively rather than expand the whole list when you call
the function. I'm not sure if this would be feasible. But it would seem to
me there would be no difference as far as the user is concerned, because you
cannot do anything before the function returnes excluding threads for the
moment.
Yes, all your language are belong us! (Even Haskell).
Where could I get a concise intro of what will be new in V6? I've been
browsing the Perl 6 development site:

http://dev.perl.org/perl6/

and while a very interesting read, it is a bit too detailed for just getting
an overview. The WIkipedia article, on the other hand, available at:

http://en.wikipedia.org/wiki/Perl_6

fits the bill mostly but I wish it would go into more details on some
constructs, oh well. I guess I'll just browse bits of both. Some middle
ground would be great, too.
 
V

Veli-Pekka Tätilä

Steven said:
You will probably find Mark-Jason Dominus' book "Higher Order Perl" of
interest.
Hey thanks for the tip. I visited the books Web site and it seems to be just
what I need. Especially as I'd like to know more about functional
programming in general, not just in the context of Perl alone. I'm planning
to give lisp a try to see how it's like.

But speaking of the book, has anyone here purchased the electronic version
of Higher-Order Perl via Amazon? URL:

http://tinyurl.com/l7bmj

I'd like to get that one but I'm wondering if the book is screen reader
accessible. Obviously it wouldn't be much help for me if it is not. I've
also got second thoughts on Acrobat due to accessibility issues, and might
like to export the contents to say plain text or OCR them for my own use.

If some of you do have the book but aren't screen reader users, which is
probable, I would really appreciate it if you could test it with a screen
reader. If interested I can give some more instructions and hints off-list,
too. As I'm not a Jaws user, trying the Supernova or HAL demo would give the
highest confidence. You can get a copy at:

http://www.dolphincomputeraccess.com/products/supernova.htm

I do have the latest beta but it should be similar to the latest official
release, as far as PDFs go.

If it turns out that the book is not accessible, another option would be to
wait for the on-line free, Wiki version to appear. The author states on his
Web-site that he is working on it. Too bad he doesn't, as far as I know,
offer the book in any other format in the mean time.

As to what's wrong with Acrobat accessibilitywise, which is a bit OT, I've
posted on this in alt.comp.blind-users lately. You can find the thread in
Google groups by searching for amazon's acrobat e-books. It should be the
first hit on the list as I posted just yesterday.
 
V

Veli-Pekka Tätilä

Hi,
A quick addition to my own message.
Most closure examples I've seen return a ref to a hash of subs <snip>
thought that returning a ref or using a hash, in addition to efficiency,
might be an integral requirement to turn subs into closures. <snip>
Ok turns out this is not the case, it is just very convenient to put those
sub-routines in a hash and return a reference to the hash. Returning just a
single sub-routine, which must be returned by reference, of course, does the
trick equally well.

I did some Googling and found a nice explanation of closures pretty quickly.
it is at:

http://www.perl.com/pub/a/2002/05/29/closure.html

Sorry for bothering you with this kind of newbie stufff <grin>. Seems
reading up on my own pays off again.
 
A

Anno Siegel

Veli-Pekka Tätilä said:
Hey thanks for the tip. I visited the books Web site and it seems to be just
what I need. Especially as I'd like to know more about functional
programming in general, not just in the context of Perl alone. I'm planning
to give lisp a try to see how it's like.

Consider going straight to Haskell instead.

Lisp is an amazing language and learning it thoroughly can change and
extend your outlook on programming. I know because I have been a
Lisp programmer (almost to the exclusion of other languages) for many
years. But, man, the language is older than Fortran! Haskell was
conceived by people with the Lisp experience under their belts. If
my instinct is any good, it is going to take over Lisp's role of "the
functional language", or already has.

Anno
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top