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.