[Petal] Does Petal support a simple while loop?
Grant McLean
grant at mclean.net.nz
Mon Jun 23 08:35:15 BST 2003
Thanks for your thoughtful reply Fergal. I share your concern
about adding too many general purpose programming structures to
Petal. Perhaps a little background on what I'm trying to achieve
might be useful...
I have a 'RecordSet' class that wraps a DBI statement handle and
(among other things) performs a fetch each time the next method
is called. For the common case, this lazy evaluation is not a
huge win, but as well a being more efficient for large result
sets, it also allows some extra funkiness that wouldn't be
possible if all the result rows were slurped into an array.
For example, if I build a record set from this query:
my $rs = $self->db_select("
SELECT artist, year, title FROM cdlist WHERE genre = ?
ORDER BY artist, year, title
", $genre
);
Then I can use this snippet in a TT2 template:
<% WHILE (artist = rs.grouped_column('artist')) %>
<h2><% artist %></h2>
<ul>
<% WHILE (row = rs.next) %>
<li><% row.title %> (<% row.year %>)</li>
<% END %>
</ul>
<% END %>
To produce HTML that renders like this:
Genre: rock
Billy Joel
* Turnstiles (1976)
* The Nylon Curtain (1982)
* An Innocent Man (1983)
* The Bridge (1986)
Crowded House
* Crowded House (1986)
Peter Gabriel
* So (1986)
* Shaking The Tree (1990)
* Us (1992)
Ideally, I'd like to achieve the same effect with Petal code like
this:
<div petal:repeat="artist rs/grouped_column 'artist'">
<h2 petal:content="artist">Artist</h2>
<ul>
<li petal:repeat="row rs/next">
<span petal:replace="row/title">Title</span>
(<span petal:replace="row/year">Year</span>)
</li>
</ul>
</div>
The grouped_column() method on the RecordSet object supports
nesting to an arbitrary level.
To achieve the grouping effect in TT2 with out the recordset
object would involve temporary variables and ugly if statements.
The same approach might work with Petal but it would be at
least as ugly.
Both the grouped_column() and the next() methods do actually
return an empty list in a list context (my description of
returning undef at the end was an oversimplification) so they
would be compatible with the simple style:
while (my ($next) = $list->next)
and would not require the has_next(), get_next() style of
implementation. I understand the full iterator model you
discuss, but since this is Perl and as you say Perl can do it
with one method, I don't see an advantage. The disadvantage
would be that a change to the Petal syntax would be required
to define the extra method names.
I had thought that the approach I'm suggesting, would
require no extra syntax beyond allowing petal:repeat's second
argument to be a method name. As I type this, I realise that
it probably already can be a method name but expects that the
method would return a list of hashes. If that is how it
behaves then my proposal probably couldn't be integrated
easily. Bother.
Must go and have breakfast :-)
Regards
Grant
Fergal Daly wrote:
> The current way to do it is to dump all those elements into an array then
> petal:repeat over it. Of course then you lose the advantages of using an
> iterator - like being able to pass around massive collections of data without
> copying and generating the next element on the fly etc. I'm all on for
> allowing Petal to use iterators and I think Perl 6 will be full of them but
> I'm not all on for general while loops in Petal. Perhaps specific support for
> iterators would be better.
>
> Allowing while loops with arbitrary conditions would be easy enough but it
> pushes Petal further towards being a very bad programming langauge rather
> than a very good templating language.
>
> In most languages, an iterator needs 2 methods, hasNext() to check if there is
> a next item and getNext() to get it when you want it. Perl can do it with
> just one method.
>
> The next() method you described will not work for collections that contain
> undef elements, so although it's OK for the collections you're dealing with,
> it can't handle the general case.
>
> To handle the general case in 1 method, you return either a 1 item list
> ($next_item) or an empty list () when there are no more items. Then do
>
> while (my ($next) = $list->next)
> {
> }
>
> This will work whether next() returns (undef) or (0) because the while will
> see a non-empty list which evaluates to true.
>
> Iterators are an excellent programming pattern so support for 1- and 2-method
> iterators in Petal would be a good thing. How about extending the repeat
> syntax to up to 4 arguments (even writing that sentence makes me cringe but
> I'll keep going)
>
> petal:repeat="item array_path getNext hasNext">
>
> with end_method being optional. Why 4 arguments? If you want to loop over a
> collection, you need somewhere to store the current item and you need to know
> how to find your collection - so far that's just the same as the old repeat
> syntax - if your collection is actually an iterator then you also need the
> name of the method for producing the next item and (possibly) the name of the
> method for telling you when to stop.
>
> 4 arguments would results in this code
>
> my $array = $hash->{"array_path"};
> while ($array->hasNext)
> {
> $hash->{"item"} = $array->getNext;
> # ...
> }
>
> 3 arguments would be the Perlish 1-method iterator and would produce this code
>
> my $array = $hash->{"array_path"};
> while (($hash->{"item"}) = $array->getNext)
> {
> # ...
> }
>
> Maybe overloading the repeat syntax is a bad idea, maybe there should be a
> petal:iterator tag.
>
> Another problem is that even though they look very similar, there's a big
> difference between
>
> petal:iterator="item it getNext"
>
> and
>
> petal:iterator="item it getNext hasNext"
>
> hasNext is not simply an optional argument, it changes the meaning of the
> first argument in a confusing way, so that it must return ($item) instead of
> $item. Maybe they should each have their own tag...
>
> I think the patches are easy, just deciding what to implelement is the hard
> part,
>
> F
>
> On Sunday 22 June 2003 00:13, Grant McLean wrote:
>
>>I know that petal:repeat can be used to iterate through an arrayref
>>of hashrefs like this:
>>
>> <ul>
>> <li petal:repeat="row hashlist">
>> <span petal:replace="row/product_id">Product ID</span>:
>> <span petal:replace="row/description">Description</span>
>> </li>
>> </ul>
>>
>>What I'd like to do is use a resultset object with a ->next() method
>>like this:
>>
>> <ul>
>> <li petal:repeat="row rs/next">
>> <span petal:replace="row/product_id">Product ID</span>:
>> <span petal:replace="row/description">Description</span>
>> </li>
>> </ul>
>>
>>ie: the expression (method call) is evaluated repeatedly until it
>>returns undef.
>>
>>Is there some way to do this already? If not, should I whip up a
>>patch?
>>
>>Regard
>>Grant
>
>
More information about the Petal
mailing list