PDL is a very general datatype. To one person, a 2D piddle may represent a collection of samples from a song while to another person a 2D piddle may represent an image. However, when you say use PDL::Image2D, various methods get installed into the PDL package, for all piddles to use. This naturally leads to the careful selection of long-winded names in a defensive approach to not getting your toes stepped on (or not stepping on somebody else's toes).

It occurred to me a few days ago that we could manage this with lexically scoped methods. I don't mean lexically scoped subroutines (i.e. Lexical::Sub) because I really like PDL's method-chaining and want to keep that. Nor do I mean lexically scoped methods that get called on ALL objects, regardless of type (i.e. Method::Lexical) because I only want to (ultimately) modify the PDL method resolution.

To achieve this, I wrote a simplistic but functional AUTOLOAD method that examines the hint hash to find mappings from method names to functions in other packages. Then, I wrote a pragma that modified the hints hash, adding a mapping. After a bit of confusion and fiddling, I finally got it to work. Here is an example to illustrate:

#lexical-methods-test.pl use strict; use warnings; use 5.010; use Foo; my $obj = Foo->new; # Look, we can call normal class methods: $obj->foo; # This will fail (which is why it's in an eval) eval { $obj->bar; 1; } or do { print "Unable to call 'bar' on Foo object in this lexical scope\n" +; }; { # Lexically allow method "bar" as a Foo method: use Bar; $obj->foo; $obj->bar; } # Out here, normal method calls still work $obj->foo; # Kaboom! $obj->bar;

Outside of the lexical scope where I say "use Bar", calling the bar method on the Foo object issues an exception. Installing the mapping is quite simple:

# Bar.pm use strict; use warnings; use 5.010; package Bar; # Installs a method called bar sub import { $^H{"Foo/bar"} = 'Bar/bar'; } sub unimport { delete $^H{"Foo/bar"}; } sub bar { print "Calling method bar from package Bar\n"; } 1;

Writing the class for which this works was a little trickier:

# Foo.pm use strict; use warnings; use 5.010; package Foo; use Carp 'croak'; sub new { my $class = shift; $class = ref($class) || $class; return bless {}, $class; } sub foo { print "Calling method foo from package Foo\n"; } sub AUTOLOAD { my $self = $_[0]; # Get the called method name and trim off the fully-qualified part ( my $method = our $AUTOLOAD ) =~ s{.*::}{}; # Get the hints hash for the calling lexical scope my $hinthash = (caller(0))[10]; if (exists $hinthash->{"Foo/$method"}) { my ($package, $package_method) = split '/', $hinthash->{"Foo/$method"}, 2; # Retrieve the subref and goto() it my $subref = $package->can($package_method) or croak("Lexically scoped Foo method $method points to a +nonexistent method ${package}::$package_method"); goto &$subref; } elsif (my $super_method = $self->SUPER::can($method)) { goto &$super_method; } elsif ($method eq 'DESTROY') { # Do nothing if we come to this. return; } else { croak("Can't locate object method \"$method\" via package \"" +. __PACKAGE__ . '"'); } } sub can { my ($self, $method) = @_; # Check if it's a lexical method my $hinthash = (caller(0))[10]; if (exists $hinthash->{"Foo/$method"}) { my ($package, $package_method) = split /\//, $hinthash->{"Foo/$method"}, 2; # Retrieve the subref and goto() it my $subref = $package->can($package_method) or croak("Lexically scoped Foo method $method points to a +nonexistent method ${package}::$package_method"); return $subref; } elsif (my $super_method = $self->SUPER::can($method)) { return $super_method; } else { return undef; } } 1;

I've created a gist with this code example on github if anybody would like to play with it. I know that getting AUTOLOAD to work correctly is tricky, at best, but I also know that PDL doesn't get much in the way of complicated class dependencies underneath it, and I think it is possible to get this to work well enough that PDL might be able to use this sort of thing.


In reply to RFC: Lexcically scoped methods, a toy example by dcmertens

Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":

  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.