Perl Best Practices

19.5. Ties

Don't tie variables or filehandles.

Ties provide a way of replacing the behaviour any type of variable, or of a filehandle. The full mechanism is described in the standard perltie documentation[*].

[*] And more extensively explained in Chapter 9 of Object Oriented Perl (Manning, 1999) or Chapter 14 of Programming Perl (O'Reilly, 2000).

Tied variables look exactly like ordinary scalars, arrays, or hashes, but they don't act exactly like them. Their whole purpose is to hide special non-standard behaviour inside a familiar interface. As such, they can be wonderfully Perlish and Lazy, making it easy (for example) to create a variable that automatically self-increments every time its value is accessed:

# Create a variable whose value cycles from zero to five... use Tie::Cycle; tie my $next_index, 'Tie::Cycle', [0..5]; # Read in monthly results... my @cyclic_buffer; while (my $next_val = prompt 'Next: ') { # Saving them in a six-month cyclic buffer... $cyclic_buffer[$next_index] = $next_val; # And printing the moving average each month... print 'Half-yearly moving average: ', sum(@cyclic_buffer)/@cyclic_buffer, "\n"; }

Every time $next_index is used as an index into @cyclic_buffer, it moves on to the next value in [0..5]. When there are no more values, it loops back to zero and starts again. So $cyclic_buffer[$next_index] is always the next element in the cyclic buffer, even though $next_index is never explicitly incremented or reset.

And that's the problem. If $next_index had been tied further away from the loop[*], it might easily seem to some maintainer that every new value is being assigned into the same element of the buffer. Tied variables make any code that uses them less maintainable, because they make normal variable operations behave in unexpected, non-standard ways.

[*] And as the code is maintained and extended, that critical tying of the variable will drift away from the code where the magic variable is used.

They're also less efficient. A tied variable is actually a wrapper around some blessed object, and so every access on any tied variable requires a method call (instead of being implemented in highly optimized C code).

Finally, tied variables can sometimes make your code less robust, as it's very easy to subtly misimplement some aspect of the expected variable behaviour.

Tied variables can always be replaced with method calls on objects:

# Create an iterator object whose value cycles from zero to five... use List::Cycle; my $index = List::Cycle->new({ vals => [0..5] });

# Read in monthly results... my @cyclic_buffer; while (my $next_val = prompt 'Next: ') {

# Saving them in a six-month cyclic buffer... $cyclic_buffer[$index->next( )] = $next_val;

# And printing the moving average each month... print 'Half-yearly moving average: ', sum(@cyclic_buffer)/@cyclic_buffer, "\n"; }

Often they can also be replaced with a simple subroutine:

# Create a subroutine whose value cycles from zero to five... { my $next = -1; my @values = (0..5); sub next_index { return $next = ($next+1) % @values } }

# Read in monthly results... my @cyclic_buffer; while (my $next_val = prompt 'Next: ') {

# Saving them in a six-month cyclic buffer... $cyclic_buffer[next_index( )] = $next_val;

# And printing the moving average each month... print 'Half-yearly moving average: ', sum(@cyclic_buffer)/@cyclic_buffer, "\n"; }

Either way, the distinctive syntax of the method or subroutine calls provides an essential clue to the distinctive behaviour of the buffer index. So either of the previous solutions will be much more comprehensible, maintainable, and reliable than a tied variable.

Категории