A long harboured niggle of mine against Template Toolkit is that it made some special rules about its fat comma that doesn't quite gel with the way that Perl uses it.
To break into example:
statement | Perl | Template Toolkit
foo( bar => 'baz' ) | foo( 'bar', 'baz' ) | foo( { 'bar', 'baz' } )
foo( bar => 'baz', 1 ) | foo( 'bar', 'baz', 1 ) | foo( 1, { 'bar', 'baz' } )
Yes, that's right, it treats => as a pair constructor, extracts the pairs, and sticks them into an anonymous array at the end of the argument list.
This used to really drive me nuts, for a number of reasons, mostly that it's a non-reversible munge.
You can try and turn things back around themselves, by writing the following preamble into your code:
sub foo {
push @_, %{ pop @_ } if @_ && ref $_[-1] eq 'HASH';
...
}
but then you're doubly screwed when the last thing you wanted to pass actually was a hash reference. Not to mention that you've completely lost the ordering that was there.
Anyhow, I'd managed to forget most of this since I don't do a lot of work with templates these days, but a recent thread on the templates list brought it all flooding back.
While thinking about this, I hit on an idea that might be useful, if only in an Acme:: way.
Consider the chained method call: $foo-$bar->baz > Let's assume
that $foo is of class Foo and $bar is of class Bar, so we can
go ahead and run that:
perl -Mstrict -e'my ($foo, $bar) = ({}, {});
bless $foo, "Foo"; bless $bar, "Bar";
$foo->$bar->baz
'
Can't locate object method "Bar=HASH(0x813f2c4)" via package "Foo" at -e line 3.
So, we just tried to call a method named after the stringification of the $bar object on the $foo object. Now assuming that nobody got clever with the stringification, we're just one step away from fun evil.
Normally I'd frown on using AUTOLOAD, but in this case it's for fun, so we'll go with it. Say we were to write Foo::AUTOLOAD around Devel::Pointer, which will allow us to get ahold of the real $bar, by chasing down its pointer address.
use strict;
package Foo;
use Devel::Pointer;
use Carp;
sub AUTOLOAD {
our $AUTOLOAD;
$AUTOLOAD =~ m{\((0x[a-z0-9]+)\)}
or croak "couldn't extract an address from $AUTOLOAD";
my $object = \deref( eval "$1" );
return $object;
}
my $foo = bless {}, "Foo";
my $bar = bless {}, "Bar";
$foo->bar->baz;
__END__
perl proof_of_evil Can't locate object method "baz" via package "Bar" at proof_of_evil line 15.
So there we are, back to as if we'd just called $bar->baz. Now that
in itself isn't hugely useful, but instead if the Foo AUTOLOAD starts
returning proxying objects then you could have a proxy which wraps the
push @_, %{ pop @_ } if ... idiom that's useful for some TT
method calls.
Which brings us to the title, and the problem of creating an API to allow people to use this repointerizing proxy pattern. (I knew I was in trouble when I woke up this morning and declared it a pattern, but it was frightfully early).
Typically I have a couple of thoughts, two of which are well-formed,
and neither of which I really like. I'll use the
push @_, %{ pop @_ } idiom as an example in these.
OO-style.
package My::Proxy;
use base qw( Class::Proxy );
sub _proxy_behaviour {
push @_, %{ pop @_ } if @_ && ref $_[-1] eq 'HASH';
}
My::Proxy->$bar->baz;
Pros: Makes it possible to hide all the evil.
Cons: Whatever name we give _proxy_behaviour, we're going to run into a clash somewhere, as with any other methods we decide we need.
Import magic
package My::Proxy;
use Class::Proxy sub {
push @_, %{ pop @_ } if @_ && ref $_[-1] eq 'HASH';
};
My::Proxy->$bar->baz;
Pro: Evil still fairly well hidden.
Cons: Import style can be a bit dense.
But I'm still chewing it over.
I have my proof-of-evil though, which makes me happy.