Dark, Dark Corners of Perl

chromatic on 2005-01-13T07:40:39

After tonight's PDX.pm meeting, in the bar, I bet Ovid that I could reimplement his Sub::Signatures by replacing the source filter with subroutine attributes. (Wisely, he believed me.)

A few minutes later, he asked idly if I knew of any reason why Perl couldn't call methods with spaces in their names. Offhand, I couldn't think of anything besides the tokenizer that prevents it, believing that gv.c cares more about null-byte termination than the \w+ness of identifiers. If you can bypass that for symbol installation and method invocation (and that's trivial), you've done it.

I hate to give away the punchline before both frightening people who didn't know such things were possible and stumping a few of the people who know it's possible but don't see the answer right away, so here are my tests. They all pass on my machine and I expect them to run just fine on at least Perl 5.8.0 and newer. (Perl 5.6 probably works, but I expect it to fail on more complicated things.) You don't need any non-core modules besides Attribute::Handlers, and I believe that's in the core in the 5.8.x family.

#!/usr/bin/perl -w

BEGIN
{
	chdir 't' if -d 't';
	use lib '../lib', '../blib/lib';
}

use strict;
use Test::More tests => 4;

my $module = 'Attribute::Scary';
use_ok( $module ) or exit;

package Hello;

use strict;
use warnings;

use Attribute::Scary;

sub new
{
	my ($class, $name) = @_;
	bless \$name, $class;
}

sub name :Method
{
	return $$self;
}

sub greet :Method
{
	return sprintf( "Hello, %s!\n", $self->name() );
}

package main;

my $hi = Hello->new( 'Bob' );
is( $hi->greet(), "Hello, Bob!\n",
	':Method attribute should autoadd invocant shift' );

my $spacey_name  = 'spacey 0';
my $spacey_greet = 'spacey 1';

is( $hi->$spacey_name(), 'Bob',
	'... also installing first method as "spacey 0"' );
is( $hi->$spacey_greet(), "Hello, Bob!\n",
	'... and second as "spacey 1"' );

There are two tricks in the implementation, one of which is an "eww, evil--and CLEVER!" trick and the other is something either you know about or you don't.


Re:

Aristotle on 2005-01-13T13:31:54

I'm not sure I don't understand the purpose of the excercise, or exactly what the code in Attribute::Scary is supposed to do that the spacey method tests look for, and what's supposed to be unusual about what I see in the tests.

So I don't know if the teste code is more clever than I am thinking or not.

Are you referring to the fact that one can install things in the package table that aren't valid identifiers and then get at them using symbolic lookup?

Re:

Aristotle on 2005-01-13T13:32:59

Err, “not sure I understand” is, of course, what I meant to say.

Re:

Ovid on 2005-01-13T15:15:44

Well, sometimes exploring the limits of a language is fun, so not everything has to be "practical." And frankly, I think this is fascinating:

sub greet :Method
{
    return sprintf( "Hello, %s!\n", $self->name() );
}

Let's see, where did that invocant come from? I know he's not using a source filter.

Re:

Aristotle on 2005-01-13T15:25:37

I'm not looking for practicality, I'm just wondering exactly which point he's proving.

As far as the invocant is concerned: PadWalker?

I give

Mr. Muskrat on 2005-01-14T04:47:32

I couldn't wrap my head around how to get $self into the subroutine using Attribute::Handlers (I'm assuming that Attribute::Scary uses it). All that I could come up with was this.

#!/usr/bin/perl
use strict;
use warnings;
use Test::More tests => 3;

package Hello;
use strict;
use warnings;

sub new {
    my ($class, $name) = @_;
    bless \$name, $class;
}

sub name {
  my $self = shift;
  return $$self;
}

sub greet {
  my $self = shift;
  return sprintf( "Hello, %s!\n", $self->name() );
}

# let the games begin
my %spacey = (
  'spacey 0' => 'name',
  'spacey 1' => 'greet',
);

sub AUTOLOAD {
  my $name = our $AUTOLOAD;
  return if $name =~ /::DESTROY$/;
  $name =~ s/.*:://;
  my $self = shift;
  my $method = $spacey{$name};
  $self->$method;
}

package main;

my $hi = Hello->new( 'Bob' );

is( $hi->greet(), "Hello, Bob!\n",
    ':Method attribute should autoadd invocant shift' );

my $spacey_name  = 'spacey 0';
my $spacey_greet = 'spacey 1';

is( $hi->$spacey_name(), 'Bob',
    '... also installing first method as "spacey 0"' );
is( $hi->$spacey_greet(), "Hello, Bob!\n",
    '... and second as "spacey 1"' );
__END__
1..3
ok 1 - :Method attribute should autoadd invocant shift
ok 2 - ... also installing first method as "spacey 0"
ok 3 - ... and second as "spacey 1"

Re:I give

Mr. Muskrat on 2005-01-14T04:49:20

And now that I hit submit I see that I didn't fix the text of the tests.

Re:I give

chromatic on 2005-01-14T07:21:36

Nice work on the AUTOLOAD; I didn't think of that. You're right about Attribute::Scary using Attribute::Handlers, though.

Expose them to the light of day

Mr. Muskrat on 2005-01-18T19:55:28

When do we get to see Attribute::Scary?

Re:Expose them to the light of day

chromatic on 2005-01-18T22:39:00

I've had three bright ideas that have caused me to rethink parts of the approach, so it could be a couple of days. I do plan on revealing the trick soon though.