I wanted different instances of a particular class to have different overloaded methods. The way I originally did this was:

use overload; sub new { my $class = shift; overload->import( %{shift()} ); return bless {}, $class; }

I wasn't too surprised to find that this didn't work. Since I'm not telling overload anything about the instance I'm working on, it must be doing something to the package it's about to be blessed into. Because of that, subsequent instances change the overloading of previous instances (not what I want).

My solution was to generate a new package for each instance. It now looks like this (with tests):

use strict; use warnings; package SelectiveOverload; use overload; my $package_suffix = 'a'; sub new { my $class = shift; my $overload = shift; my $package = __PACKAGE__; my $new_package = join '::', $package, $package_suffix++; my $mkpackage = <<"SELECTIVE_OVERLOAD"; package $new_package; overload->import( \%{\$overload} ); \@$new_package\::ISA = ( '$package' ); SELECTIVE_OVERLOAD eval $mkpackage; die $@ if $@; return bless {}, $new_package; } package main; use Test::More 'tests' => 9; use Scalar::Util qw( reftype ); my $over_array_called = 0; my $over_array = SelectiveOverload->new( { '@{}' => sub { $over_array_called = 1; [] } } ); isa_ok( $over_array, 'SelectiveOverload' ); ok( !$over_array_called, 'array overload sub not called' ); my @x = @{$over_array}; ok( $over_array_called, 'array overload sub was called' ); ok( !eval { my $test = ${$over_array}; 1 }, 'dereference scalar on $over_array fails' ); $over_array_called = 0; my $over_scalar_called = 0; my $over_scalar = SelectiveOverload->new( { '${}' => sub { $over_scalar_called = 1; \my $x } } ); isa_ok( $over_scalar, 'SelectiveOverload' ); ok( !$over_scalar_called, 'scalar overload sub not called' ); my $x = ${$over_scalar}; ok( $over_scalar_called, 'scalar overload sub was called' ); ok( !eval { my @test = @{$over_scalar}; 1 }, 'dereference array on $over_scalar does not work' ); ok( !$over_array_called, 'array overload sub not called' );

The last two tests are the ones that failed in my initial implementation.

One thing I haven't done (but maybe could do) is memoize the package names based on the overload options presented. That would be difficult to do generally, though, since I have to match the sub refs to each other.

I'm wondering if any other monk has any experience to share about this. My understanding over overloading is a bit hazy, so I wonder if I've just shot myself in the foot somehow.

Replies are listed 'Best First'.
Re: Overloading different instances differently.
by tilly (Archbishop) on Feb 22, 2008 at 22:12 UTC
    Your solution should work. Its biggest drawback is that every object created clears Perl's cache of method lookups. Also it leaks memory - create a thousand objects and Perl now has a thousand classes that never go away.

    Memoizing would solve both of those problems. Matching sub refs isn't that hard. The following code will turn your hash into a string suitable for memoizing.

    use Scalar::Util qw(refaddr); sub overload_string_specification { my $overload = shift; join " ", map { ($_=>refaddr($overload->{$_})) } sort keys %$overload; }
    Another solution is to overload all of the methods you may wish to be able to overload with methods that dynamically figure out the right function to call. If you only have a small number of methods that you'll be overloading this isn't a bad route. But if you could potentially wind up overloading all of the possible things in overload, then doing this basically means re-creating all of the dispatch logic in overload in your code. Which you may not want to do.
      I could be wrong, but the memory problem doesn't seem to be unfixable.

      The packages the OP is creating would be small (because all the methods are inherited). Also you could manually get rid of the package on destruction using something akin to this:
      package FOO; sub asdf{shift ; reverse @_}; package main; # need an object so that functions get bound at runtime my $o = bless {}, 'FOO'; print $o->asdf(1..10); # ok undef %FOO::; print $o->asdf(1..10); # function cannot be found
      If you were extremely memory concious, you could put each instance's fields in the new package (why use another hashref?)

      If you wanted to be realy sneaky, you could have the object as a globref to it's own package and the decrement the refcount on the glob by one. When the instance goes out of scope, the glob's refcount drops to 0 and cleans itself up.

      At least thats the way I understand packages. Please enlighten me if I'm wrong.

      Udate: The code presented above doesn't work. Here's why: every time perl sees the name *Foo:: or %Foo:: in the source, it increments the ref count of it (similar in concept to closing over a value, but for globals). Therefore undef *Foo:: inadvertently creates another reference to the value it is trying to destruct.

      Here's the code I used to come to that conclusion.
      use strict; use Inline 'C'; $\ = "\n"; my $x; BEGIN { my $n = 100; $x = eval 'sub {' . ('%Foo::;' x $n) . '}'; } print refcount(*Foo::); # prints $n + 2 __END__ __C__ int refcount(SV* x) { return SvREFCNT(x); }
        You are assuming that your code destroys everything in a package properly. Let's test that:
        #! /usr/bin/perl -w use strict; my $s; print "Creating functions\n"; my $x = TellDestroy->new(sub {print $s}, "variable sub"); *Foo::function = TellDestroy->new(sub {print $s}, "glob sub"); print "Clearing variable\n"; undef $x; print "Clearing glob\n"; undef %Foo::; print "Exiting\n"; package TellDestroy; use Scalar::Util qw(refaddr); my %function_name; sub new { my ($class, $self, $name) = @_; $function_name{refaddr($self)} = $name; bless $self, $class; } sub DESTROY { my $self = shift; my $name = delete $function_name{ refaddr($self) }; print STDERR "$name is going away\n"; } __END__
        produces the following output for me on Perl 5.8.6 under Linux:
        Name "Foo::function" used only once: possible typo at check line 8. Creating functions Clearing variable variable sub is going away Clearing glob Exiting glob sub is going away
        So the function survived to global destruction. I take that to mean that Perl doesn't clean up packages the way you wanted it to.

      Thank you!

      Part of the point of this exercise is for the object to "look right" to overload::Method, if anyone cares to look. I don't want it to look like @{} is available if it's one of the unimplemented methods on that particular instance.

      Your point about leaking packages all over is well-taken. I suspect that "looks right" functionality is less important than that. Perhaps I'll implement both solutions and let the user specify which behavior they want (with details of the trade-offs in the documentation).

Re: Overloading different instances differently.
by Fletch (Bishop) on Feb 22, 2008 at 22:08 UTC

    Seems like overkill. Overloading is just a special syntax for a behind the scenes method call. Just make your method call implementing the overload examine the instance its called on and vary what it does accordingly instead.

    Simplest implementation that comes to mind is have the name of the overload method you really want called on the method stored in the instance. If that's too constraining you could also store a coderef for the behavior instead and have the overload method just call that.

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Problem. Suppose for one object you overload '""'. With his solution you've also overloaded 'bool' and '0+' in the way the user of the code probably expects. With your solution all of those implicit overloads have to be spelled out. Or else you've got to build all of the dispatch logic that is already built into overload.

        Erm, no. I'm just talking about making the method called by the normal overload call examine the instance in question to decide what to do. I don't see how that would change anything about the automagically generated boolification or numification logic.

        #!/usr/bin/perl use strict; use warnings; package Ovl; use overload '""' => \&mystring; sub new { my $ret = bless {}, shift(); $ret->{_name} = shift; $ret->{_str_code} = \&_str_default; return $ret; } sub _str_default { my $self = shift; "!" . $self->{_name} . "!" } sub mystring { my $self = shift; my $str_code = $self->{_str_code} || \&_str_default; $self->$str_code( @_ ); } package main; my $o = Ovl->new( "default" ); print "$o\n"; my $o_undef = Ovl->new( undef ); $o_undef->{_str_code} = sub { "I R UNDEFINED" }; print "$o_undef\n" if $o_undef; my $o_numeric = Ovl->new( 15 ); $o_numeric->{_str_code} = sub { sprintf( "%0.5f", shift->{_name} ) }; print "$o_numeric\n" if int( $o_numeric ) == 15; exit 0; __END__

        Update: AAAAAHHH. Just reread the OP's code and I see what you're getting at. In order to implement arbitrary overload operations on instances (i.e. what operations are overloaded on the instance, not just what each operation does on each instance) you would need to have defined handlers for all possible operations for the class itself and then yes you would need duplicate dispatching logic. I was thinking of the case where each instance shared the same set of overloaded operations, whereas his allows instance level selection of what's overloaded. I just glanced over his code and missed that he was trying to get different sets of overload behavior per instance. Bah, it's quitting time on Friday.

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.