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.
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!';
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.", );
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 | |
by Narveson (Chaplain) on Jun 27, 2008 at 12:38 UTC | |
|
Re: Motivation for replacing object methods at runtime
by moritz (Cardinal) on Jun 26, 2008 at 15:20 UTC | |
|
Re: Motivation for replacing object methods at runtime
by Arunbear (Prior) on Jun 26, 2008 at 15:21 UTC | |
|
Re: Motivation for replacing object methods at runtime
by BrowserUk (Patriarch) on Jun 26, 2008 at 15:54 UTC | |
by Narveson (Chaplain) on Jun 27, 2008 at 06:23 UTC | |
by BrowserUk (Patriarch) on Jun 27, 2008 at 06:59 UTC | |
|
Re: Motivation for replacing object methods at runtime
by lodin (Hermit) on Jun 27, 2008 at 20:47 UTC | |
|
Re: Motivation for replacing object methods at runtime
by Pic (Scribe) on Jun 29, 2008 at 20:50 UTC |