Perl Best Practices

15.15. Coercions

Always consider overloading the boolean, numeric, and string coercions of objects.

When an object reference is used as a boolean, it always evaluates to true by default, so:

croak( q{Can't use non-zero value} ) if $fuzzynum;

always throws an exception, even when $fuzzynum contains 0±0.

An even more serious problem arises when object references are treated as numbers: by default, they numerify to the integer value of their memory address. That means that a statement like:

$constants[$fuzzynum] = 42;

is really something like:

$constants[0x256ad1f3] = 42;

which is:

$constants[627757555] = 42;

which will almost certainly segfault when it tries to allocate six hundred million elements in the @constants array.

A similar problem arises if an object is used where a string is expected:

my $fuzzy_pi = Num::Fuzzy->new({val => 3.1, plus_or_minus => 0.0416}); # And later... print "Pi is $fuzzy_pi\n"; # $fuzzy_pi expected to interpolate a string

In a string context, the object's reference is converted to a debugging value that specifies the class of the object, its underlying data type, and its hexadecimal memory address. So the previous print statement would print something like:

Pi is Num::Fuzzy=SCALAR[0x256ad1f3]

The developer was probably hoping for something more like:

Pi is 3.1 ± 0.0416

All of these problems occur because objects in Perl are almost always accessed via references. And those references behave like objects only when they're specifically used like objects (i.e., when methods are called on them). When they're used like values (as in the examples), they behave like reference values. The resulting bugs can be particularly hard to discover, and even harder to diagnose once they're noticed.

It's good practice to overload the boolean, numeric, and string coercion behaviour of objects to do something useful and expected. For example:

package Num::Fuzzy; use charnames qw( :full ); { use overload (

# Ignore the error range when converting to a number... q{0+} => sub { my ($self) = @_; return $self->get_value( ); },

# Only true if the range of possible values doesn't include zero... q{bool} => sub { my ($self) = @_; return ! $self->range_includes(0); },

# Convert to string using the as_str( ) method... q{""} => sub { my ($self) = @_; return $self->get_value( ) . "\N{PLUS-MINUS SIGN}" . $self->get_fuzziness( ); },

# Use Perl standard behaviours for other operations... fallback => 1, );

# etc. }

In many classes, the most useful thing to do is simply to signal that attempting the coercion was a bad idea:

package Process::Queue; use Carp; { use overload (

# Type coercions don't make sense for process queues... q{0+} => sub { croak( q{Can't numerify a Process::Queue } ); }, q{bool} => sub { croak( q{Can't get the boolean value of a Process::Queue } ); }, q{""} => sub { croak( q{Can't get the string value of a Process::Queue } ); },

# Use Perl standard behaviours for other operations... fallback => 1, );

# etc. }

This last example, suitably adapted, makes an excellent default for any class.

Категории