On Jan 21, 2008 11:04 AM, Andy Wardley <[EMAIL PROTECTED]> wrote:
> mike wrote:
> > however, it appears that TT3 has not been touched in over two
> > years.
>
> Hi Mike,
>
> Actually, it's more the case the the TT3 web site hasn't been touched
> in over two years. I _have_ been touching TT3 in that time (honest!)
> although not as much as I would have liked due to commitments to
> various projects for other people.
mmmhmmm, suuuure. :)
> However, my work schedule is now relatively clear so I'm doing a fair
> bit of TT3 touching right now. I'm hoping this will be the last burst
> to get it out the door in the next few months.
i look forward to it.
> > rather than wait for TT3, i am proffering the attached unintrusive
> > patch to satiate those (such as myself) whom are suffering from
> > moderate to severe insanity arising from the use of template
> > toolkit and code that uses wantarray.
>
> Count me among those people. It's probably of little consolation, but
> I kick myself hard every time I get bitten by it. Consider me
> suitably punished.
honestly, i don't believe it to be TT's fault. the blame lies squarely
with DBIx::Class. i loathe wantarray and DBIx::Class's liberal use of
it. those of you familiar with DBIx::Class may know that relationship
accessors or the ResultSet->search method will "optimize" for you by
assuming that you want a list of objects (as would be returned by the
ResultSet->all method) if called in list context. while this may
typically be the case, it is not always so. this "optimization" robs
the user of flexibility and control in exchange for not having to type
->all when you really mean it. in this case, DWIM fails to DTRT.
> > it's a relatively simple bolt-on enhancement. it passes all the
> > tests. it doesn't severely alter the existing grammar. it doesn't
> > cause people muscle fatigue, gastroesophageal reflux disease, or
> > lymphoma. it allows people out there working with template toolkit
> > and DBIx::Class (in addition to other wantarray-loving code) to
> > severely reduce their stress levels and become more productive
> > members of their development teams.
>
> You present a compelling case. :-)
clinical trials also indicate a 26% reduction in occurrences of migraines,
but this statement has not yet been approved by the health department of
your respective sovereign state.
> The patch itself is fine and I think it addresses a problem that
> definitely needs to be solved. As you mention, I've been promising
> better support for this in TT3, although until today I hadn't quite
> figured out how that was going to manifest itself (apart from making
> scalar context the default, but then we've just inverted the problem).
>
> The main issue that I can see with with the SCALAR directive is that
> it only addresses part of the problem. For example, you still can't
> do things like this:
>
> [% FOREACH track IN album.tracks %]
>
> where you want the tracks() method called in scalar context against
> the album object.
i don't know why you'd want to do such a thing, but i suppose that giving
the user enough rope to hang themselves with is important.
> So I sent a few hours today thinking hard about this and came up with a
> simple and elegant solution (if I may say so myself) that appears to
> implement everything we need from a plugin.
>
> Like this:
>
> [% USE scalar %]
> [% object.scalar.method %] # calls object.method in scalar context
>
> The scalar plugin defines a .scalar vmethod for hashes and lists (so
> that blessed list objects can use it too). The .scalar dotop returns an
> object (a "monad" in functional programming lingo, if my understanding of
> the terminology is correct) which "wraps" the original object on its left.
> The monad object has an AUTOLOAD method which simply delegates the .method
> call to the original object, calling it in a scalar context, of course.
>
> The nice thing about implementing it as a virtual method is that it works
> everywhere:
your solution is quite elegant; i worried though about the potential
performance implications of instantiating a new object for each call.
take, for instance, a ResultSet chain i am currently using in a category
listing template:
[%
category.call_method_scalar('products').call_method_scalar('search_related',
'authorizations').call_method_scalar('magazine').call_method_scalar('trade').call_method_scalar('collecting').count
%]
call_method_scalar() is a method i have added to my ResultSet and Row
subclasses in order to call the specified method in scalar context.
your solution would reduce it to:
[%
category.scalar.products.scalar.search_related('authorizations').scalar.magazine.scalar.trade.scalar.collecting.count
%]
which is MUCH better, but at the cost of instantiating a wrapper object
with an AUTOLOAD method. my patch would reduce it further:
[% SCALAR
category.products.search_related('authorizations').magazine.trade.collecting.count
%]
without the overhead of a plugin.
i ran a series of three tests against trunk with my patch applied.
using Devel::Timer, i processed a template for each one of the three
paradigms:
my $n = 500;
my $a = '[%
category.call_method_scalar(\'products\').call_method_scalar(\'search_related\',
\'authorizations\').call_method_scalar(\'magazine\').call_method_scalar(\'trade\').call_method_scalar(\'collecting\').count
%]' x $n;
my $b = '[% USE scalar %]' . '[%
category.scalar.products.scalar.search_related(\'authorizations\').scalar.magazine.scalar.trade.scalar.collecting.count
%]' x $n;
my $c = '[% SCALAR
category.products.search_related(\'authorizations\').magazine.trade.collecting.count
%]' x $n;
my $data;
$timer->mark('start');
$tmpl->process(\$a, $stash, \$data) || die $tmpl->error;
$timer->mark('DBIx::Class instrumentation');
$tmpl->process(\$b, $stash, \$data) || die $tmpl->error;
$timer->mark('Plugin::Scalar instrumentation');
$tmpl->process(\$c, $stash, \$data) || die $tmpl->error;
$timer->mark('SCALAR directive instrumentation');
$timer->report;
which produced the following reports:
for $n = 500:
Interval Time Percent
----------------------------------------------
01 -> 02 2.6105 34.85% start -> DBIx::Class instrumentation
02 -> 03 2.5764 34.40% DBIx::Class instrumentation ->
Plugin::Scalar instrumentation
03 -> 04 2.2892 30.56% Plugin::Scalar instrumentation -> SCALAR
directive instrumentation
00 -> 01 0.0137 0.18% INIT -> start
for $n = 10:
Interval Time Percent
----------------------------------------------
01 -> 02 0.0805 41.29% start -> DBIx::Class instrumentation
02 -> 03 0.0542 27.80% DBIx::Class instrumentation ->
Plugin::Scalar instrumentation
03 -> 04 0.0472 24.22% Plugin::Scalar instrumentation -> SCALAR
directive instrumentation
00 -> 01 0.0131 6.70% INIT -> start
the times were consistent. i think $n = 10 most closely models our real
world usage. i don't think the grammar mod offers much incentive in the
speed case to make it worthy of consideration over the flexibility of the
Plugin::Scalar approach. my only argument now is one purely of a cosmetic
nature, which doesn't stand up against the aesthetical arguments of the
plugin approach.
in any event, Plugin::Scalar gives me a 30% reduction in processing time
for the n=10 test case, which i'll take with a smile.
> Have a play and see what you think. If we're happy that this adequately
> solves the problem then I'll tidy it up, add some documentation and
> include it in the next release.
i'm happy with it. though the chain of scalar calls seems a bit wordy,
it's much better than what i'm doing now.
> Thank you for the patch all the same. I appreciate the time and effort
> you spent on it, even if we don't actually end up using it. If nothing
> else, it forced me to address the underlying problem and (hopefully) fix
> it properly.
likewise. i certainly appreciate the time you spent on it as well. the
fact that you are attentive and responsive to your users is another item
to add to the list of things i like about TT.
-mike
_______________________________________________
templates mailing list
[email protected]
http://mail.template-toolkit.org/mailman/listinfo/templates