Hack 93. Return Active Values


Return values that automatically change as you use them.

The Contextual::Return module [Hack #92] has another very powerful trick up its sleeve. The scalar values it returns don't have to be constants; they can be "active." An active value is one that adapts itself each time it is evaluated. This is useful for performing initialization, cleanup, or error-handling code without forcing the caller to do anything special.

The Hack

For example, you can create a subroutine that returns a value that automatically tracks the elapsed time between events:

use Contextual::Return; use Time::HiRes qw( sleep time );      # Allow subsecond timing # Subroutine returns an active timer value... sub timer {     my $start = time;                  # Set initial start time     return VALUE                       # Return an active value that...     {                              my $elapsed = time - $start;   #    1. computes elapsed time         $start      = time;            #    2. resets start time         return $elapsed;               #    3. returns elapsed time     } } # Create an active value... my $process_timer = timer( ); # Use active value... while (1) {     do_some_long_process( );     print "Process took $process_timer seconds\\n"; }

Because the timer( ) subroutine returns a contextual value that is computed within the VALUE block itself, that returned value becomes active. Each time the value of $process_timer is reevaluated (in the print statement), the value's VALUE block executes, recomputing and resetting the value stored in $process_timer.

Running the Hack

Of course, the real advantage here is that you can have the subroutine create two or more timers for you:

my $task_timer    = timer( ); my $subtask_timer = timer( ); for my $task (@tasks) {     print "Performing $task...\\n";     for my $subtask ($task->get_subtasks( ))     {         $subtask->perform( );         print "\\t$subtask took $subtask_timer seconds\\n";     }     print "Finished $task in $task_timer seconds\\n\\n"; }

to produce something like:

$ perl do_tasks.pl Performing set-up...     Finding files took 0.775737047195435 seconds     Reading files took 0.985733032226562 seconds     Verifying data took 0.137604951858521 seconds Finished set-up in 1.98483791351318 seconds Performing initialization...     Creating data structures took 0.627048969268799 seconds     Cross-correlating took 2.756386041641235 seconds Finished initialization in 3.45225400924683 seconds etc.

Hacking the Hack

Active values can use all the other features of the Contextual::Return module. In particular, they can still be context-sensitive. For example, you could create a safer version of the built-in open function, where "safer" means that this version will return a filehandle that explodes catastrophically if you ever try to use the handle without first verifying that it was opened correctly.

Implement it like this:

use Contextual::Return; sub safe_open {     my ($mode, $filename) = @_;     my $user_has_tested   = 0;     # Open a filehandle and remember where it was opened...     open my($filehandle), $mode, $filename;     my $where = sprintf("'%s' line %s", (caller)[1,2]);     # Return an active value that's only usable after it's been tested...     return (         BOOL         {             $user_has_tested = 1;             return defined $filehandle;         }         DEFAULT         {             croak "Used untested filehandle (opened at $where)"                 unless $user_has_tested;             return $filehandle;         }     ) }

The safe_open subroutine expects two arguments: the opening mode and the name of the file to open:

my $fh = safe_open '<', $some_file;

The returned value acts like a filehandle in all contexts, but only after you have tested the value in a boolean context. Accessing the returned value in a boolean context invokes the value's BOOL block, which actively sets the $user_has_tested flag true. If you try to use the filehandle before you've tested it:

my $fh    = safe_open '<', $some_file; my $input = <$fh>;          # Use of untested return value                             # invokes DEFAULT block

the BOOL block will not have run, so the internal flag will still be false, and the value's DEFAULT block will throw an exception:

$ perl demo.pl Used untested filehandle (opened at 'demo.pl' line 12) at demo.pl line 14

If however, the filehandle has been tested in any boolean context:

my $fh = safe_open '<', $some_file     or croak "Couldn't open $some_file";   # the 'or' evaluates $fh in a                                            # boolean context so it invokes                                            # returned value's BOOL block my $input = <$fh>;          # Invokes returned value's DEFAULT block

then the value's BOOL block will have set the $user_has_tested flag. Once the flag is set, the DEFAULT block will thereafter return the filehandle without detonating.

Of course, this is incompatible with the use of Fatal as shown in "Write Less Error-Checking Code" [Hack #91].



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net