Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

RFC: Lexcically scoped methods, a toy example

by dcmertens (Scribe)
on Jun 11, 2013 at 21:25 UTC ( [id://1038345]=perlmeditation: print w/replies, xml ) Need Help??

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.

Thoughts?

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://1038345]
Approved by Corion
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others taking refuge in the Monastery: (2)
As of 2024-04-24 23:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found