Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Motivation for replacing object methods at runtime

by Narveson (Chaplain)
on Jun 26, 2008 at 14:37 UTC ( [id://694163]=perlmeditation: print w/replies, xml ) Need Help??

One of us asked earlier this week how to replace object methods at runtime, and another of us wondered why. Here is an example.

Sometimes we care what numbers are divisible by some given divisor. We can say

# encapsulate the mechanics of divisibility testing use Divisor; my $seven = Divisor->new(7); ok( $seven->divides(42), 'Seven divides 42.' ); ok( not( $seven->divides(365) ), 'The year does not contain a whole number of weeks.', ); ok( $seven->divides( 400*365 + 97 ), 'But the Gregorian calendar repeats itself every 400 years.', );

As you might expect, Divisor has been implemented to convert strings to numbers in Perl's usual seamless way:

my $two = Divisor->new(2); ok( $two->divides('42'), "Two divides '42'." ); ok( not( $two->divides('19') ), "Two does not divide '19'.", );

Now for a contrived example.

my $long_string_of_twos = '2' x 320; TODO: { local $TODO = 'Redefine divisibility by two.'; ok( $two->divides($long_string_of_twos), "Two divides $long_string_of_twos.", ) or diag 'We just contrived an overflow exception.'; }

This should not require Math::Big. Runtime polymorphism to the rescue:

# just look at the last digit, silly! $two->set_divisibility_test( sub {shift =~ /[02468]$/} ); ok( $two->divides($long_string_of_twos), "Two divides $long_string_of_twos, as any fool can see.", );

So that's what runtime polymorphism might be good for. In this particular case, I like it better than the classic subclassing approach.

Before I insert an implementation of the Divisor interface, let me invite the brethren to show how it might be implemented using Moose or other fresh approaches.

Divisor.pm:

use strict; use warnings; package Divisor; use Carp; sub new { my ($class, $divisor) = @_; croak "Not an integer: $divisor" if $divisor != int $divisor; my $instance = bless \$divisor => $class; $instance->_init($divisor); } sub _init { my ($instance, $divisor) = @_; $instance->set_divisibility_test( sub { my ($dividend) = @_; my $remainder = $dividend % $divisor; return ($remainder == 0); } ); return $instance; } my %divisibility_test_for; sub set_divisibility_test { my ($self, $code_ref) = @_; croak "Not a CODE ref: $code_ref" if ref $code_ref ne 'CODE'; $divisibility_test_for{$self} = $code_ref; } sub divides { my ($self, $dividend) = @_; croak "Not an integer: $dividend" if $dividend != int $dividend; $divisibility_test_for{$self}($dividend); } 'Divide et impera!';

Divisor.t

use strict; use warnings; use Test::More tests => 13; use_ok 'Divisor'; my $seven = Divisor->new(7); isa_ok( $seven, 'Divisor' ); ok( $seven->divides(42), 'Seven divides 42.' ); ok( not( $seven->divides(365) ), 'The year does not contain a whole number of weeks.', ); ok( $seven->divides( 400*365 + 97 ), 'But the Gregorian calendar repeats itself every 400 years.', ); my $two = Divisor->new(2); isa_ok( $two, 'Divisor' ); ok( $two->divides('42'), "Two divides '42'." ); ok( not( $two->divides('19') ), "Two does not divide '19'.", ); my $long_string_of_twos = '2' x 320; TODO: { local $TODO = 'Redefine divisibility by two.'; ok( $two->divides($long_string_of_twos), "Two divides $long_string_of_twos.", ) or diag 'We just contrived an overflow exception.'; } can_ok( $two, 'set_divisibility_test' ); # just look at the last digit, silly! $two->set_divisibility_test( sub {shift =~ /[02468]$/} ); ok( $two->divides('42'), "Two divides '42'." ); ok( not( $two->divides('19') ), "Two does not divide '19'.", ); ok( $two->divides($long_string_of_twos), "Two divides $long_string_of_twos, as any fool can see.", );

output of Divisor.t

ok 1 - use Divisor; ok 2 - The object isa Divisor ok 3 - Seven divides 42. ok 4 - The year does not contain a whole number of weeks. ok 5 - But the Gregorian calendar repeats itself every 400 years. ok 6 - The object isa Divisor ok 7 - Two divides '42'. ok 8 - Two does not divide '19'. not ok 9 - Two divides 22222222222222222222222222222222222222222222222 +2222222222 2222222222222222222222222222222222222222222222222222222222222222222222 +2222222222 2222222222222222222222222222222222222222222222222222222222222222222222 +2222222222 2222222222222222222222222222222222222222222222222222222222222222222222 +2222222222 22222222222222222222222. # TODO Redefine divisibility by two. # Failed (TODO) test (Divisor.t at line 32) # We just contrived an overflow exception. ok 10 - Divisor->can('set_divisibility_test') ok 11 - Two divides '42'. ok 12 - Two does not divide '19'. ok 13 - Two divides 22222222222222222222222222222222222222222222222222 +2222222222 2222222222222222222222222222222222222222222222222222222222222222222222 +2222222222 2222222222222222222222222222222222222222222222222222222222222222222222 +2222222222 2222222222222222222222222222222222222222222222222222222222222222222222 +2222222222 22222222222222222222, as any fool can see.

Replies are listed 'Best First'.
Re: Motivation for replacing object methods at runtime
by perrin (Chancellor) on Jun 26, 2008 at 18:20 UTC
    I'd suggest using delegation to do this instead, i.e. you can delegate the implementation of a method to another class and change the delegation at runtime. This is a widely accepted OO practice, unlike tinkering with replacing methods.

      Thanks for this suggestion.

      An example (in Perl) would be splendid.

      In the meantime, I asked Google, and perl delegate method implementation change delegation at runtime referred me to ObjectivePerl and Moose, among other things.

      Speaking of Moose, the original seeker was referred to Moose, among many other CPAN modules. An apt Moose example is one of the things I hoped this post might elicit.

      Delegation is a good word, and terminology is important, because

      • good terminology helps to clarify one's own thinking, and
      • approved terminology reassures others that one is not just making stuff up.

      Another really good term is dispatch table. In some cases, I am sure the best implementation of an OO method call is just to hand over the work to a nice lexical hash. Then the question would merely be replacing hash entries at runtime.

Re: Motivation for replacing object methods at runtime
by moritz (Cardinal) on Jun 26, 2008 at 15:20 UTC
    So that's what runtime polymorphism might be good for. In this particular case, I like it better than the classic subclassing approach.

    So your point is that you don't like polymorphism as provided by standard OO (at least in this case), and therefore decide to re-implement it manually?

    Somehow that fails to convince me, and painfully reminds of attempts seen here at perlmonks to re-implement pack or unpack, CGI::param(), various core modules and other stuff.

    I like the Haskell's approach of pattern matching against signatures:

    divides x 2 = $(special cased implementation) divides x y = $(general-cased implementation)

    It seems that to me that replacing object methods at runtime is a rather limited solution to this problem, because it works only for methods (not for subs, although they'd profit from the same mechanism), and more importantly, it only works for special-casing on the object. As soon as you want to special-case on the parameter (say, you know it's a prime, and thus always return false).

    I don't know what's the best perlish approach is, but re-implementing an existing technique and thereby retaining the limitations doesn't exactly seem right.

Re: Motivation for replacing object methods at runtime
by Arunbear (Prior) on Jun 26, 2008 at 15:21 UTC

    The divisibility_test is just a (lexical) coderef, not an object method, so you haven't really replaced any object methods.

    Also your _init method does not need to have the $divisor passed to it because you could just retrieve it by dereferencing $instance.

    Once you call set_divisibility_test(), your default divisibility_test is lost, with no way to get it back. It may be better to handle this by letting divides() take an optional coderef and use your default one if none is supplied.

    And you'll need a DESTROY method to remove $divisibility_test_for{$self} when the object goes out of scope, or you'll be leaking memory.

Re: Motivation for replacing object methods at runtime
by BrowserUk (Patriarch) on Jun 26, 2008 at 15:54 UTC

    Sorry, but if you think that using a whole module, and the runtime replacement of methods, to test for divisibility by 2 (or any number), is a persuasive argument for this obscure and dubious feature, then I guess we live on different planets.

    And labelling what you do above with the term 'runtime polymorphism' is just plain wrong.

    Finally, obscuring your argument with all that pointless Test::More stuff, does nothing to strengthen it. It simply serves to obscure the crude, sledgehammer-to-crack-a-nut nature of what you are doing.

    There are some good examples out there of where runtime reassignment of object methods can be useful, but this is possibly the very worst hypothetical, would-never-be-used-for-real-work example I've yet seen.


    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      There are some good examples out there of where runtime reassignment of object methods can be useful

      Oh, I'm so glad.

      If somebody wanted to share one of the good examples, or just link to it, that would be awesome.

Re: Motivation for replacing object methods at runtime
by lodin (Hermit) on Jun 27, 2008 at 20:47 UTC

    If Perl had better support for anonymous classes a simple solution would be to just create a specialization of the Divisor class (i.e. a subclass).

    # Pseudo code! my $two = new subclass of Divisor { sub divides { $_[1] =~ /[02468]\z/ } };
    There would be no need to modify or redefine anything in the Divisor class.

    lodin

Re: Motivation for replacing object methods at runtime
by Pic (Scribe) on Jun 29, 2008 at 20:50 UTC

    I think a better motivation for being able to replace subroutines dynamically is AOP. When you can manipulate the dispatch tables like this, some flavours of AOP become trivially easy to implement, simply insert an appropriate function and you're done (this works for any advice that only needs access to the subroutine's parameters).

    Other kinds of AOP are a bit more difficult to implement, but as long as you want to apply advice on subroutines, you don't need to proxy objects (like it's often done in Java).

Log In?
Username:
Password:

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

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

    No recent polls found