@digits = (0..9);
####
while ($data = &func) {
&munge ($data);
}
##
##
if (&setup) {
while ($data = &advance) {
&munge ($data);
}
&teardown;
} else {
## life sucks
}
##
##
while (&setup_and_teardown .. &advance) {
&munge (&get_data);
}
##
##
#!/usr/bin/perl -w
package Loop_control;
## define some constants used in the simulation:
$LIMIT = 5; ## maximum number of iterations
$START_ERR = 0.25; ## 25% chance of failing to start
$LOOP_ERR = 0.1; ## 10% chance of failing per iteration
$FATAL = 0.15; ## 15% chance of fatal errors
## new (nil) : Loop_control_ref
#
# yer basic constructor. bless the ref, call init(), and return
# the result.
#
# general note regarding style: i've outdented all the
# print() statements that generate tracking output. it's a
# personal thing.. i find s/^print/# print/ easier than sifting
# through the code trying to find that one blasted debugging
# statement.
#
sub new {
print "-- Loop_control::new\n";
my $self = bless {}, shift;
return ($self->init);
}
## init (nil) : Loop_control_ref
#
# sets up a couple utility variables, but doesn't do anything
# earth-shattering.
#
sub init {
print "---- Loop_control::init\n";
my $self = shift;
$self->{'state'} = 'new'; ## condition register
$self->{'msg'} = ''; ## this object's $!
return ($self);
}
## updown (nil) : boolean
#
# the range operator calls this routine on the first and last
# passes through the loop. this routine delegates control to
# setup() or teardown(), based on the contents of the state
# register.
#
sub updown {
print "-- Loop_control::updown\n";
my $self = shift;
if ($self->{'state'} eq 'new') { ## have we been here yet?
$self->{'state'} = 'running';
return ($self->setup); ## no.. set things up
} else {
return ($self->teardown); ## yes.. tear things down
}
}
## setup (nil) : boolean
#
# set up the data source. this could be any procedure that
# might fail, like opening a file or a network connection.
# this toy version just fails randomly so you can see the
# overall system work.
#
# this routine returns TRUE if the setup succeeds, thus
# making the range operator test TRUE the first time it's
# polled.
#
sub setup {
print "---- Loop_control::setup - ";
my $self = shift;
if (rand() > $START_ERR) {
$self->{'count'} = 1; ## trivial setup
print "TRUE\n";
return (1);
} else {
print "FALSE - FAIL - FAIL - FAIL -\n";
$self->{'state'} = (rand() < $FATAL) ? 'fatal' : 'error';
$self->{'msg'} = 'failed during setup';
return (0);
}
}
## teardown (nil) : boolean
#
# shut down the data source. this routine terminates the loop,
# but shouldn't fail in any way that will ruin the data.
#
# this routine returns FALSE, thus making the range operator
# test FALSE as well, thus ending the loop.
#
sub teardown {
print "---- Loop_control::teardown - FALSE\n";
return (0);
}
## advance (nil) : boolean
#
# this routine fetches the next chunk of data. it can fail
# in ways that will ruin the transaction, so once again we
# simulate failure by rolling dice.
#
# this routine returns FALSE on success, which seems wierd
# until you recall that the range operator is asking,
# "have we hit a stopping point yet?"
#
# $self->{'data'} is a read-only inspection variable. it
# does the same thing an accessor method get_data() would,
# but doesn't require a function call. it's an indulgence
# i grant myself when i'm damsure i can get away with it.
# no code anywhere in this package reads $self->{'data'},
# so even if a user does screw around with it, their change
# will have no effect on the object's behavior.
#
sub advance {
print "-- Loop_control::advance - ";
my $self = shift;
if (rand() < $LOOP_ERR) { ## short-circuit on error
print "TRUE - FAIL - FAIL - FAIL -\n";
$self->{'state'} = (rand() < $FATAL) ? 'fatal' : 'error';
$self->{'msg'} = "failed during pass $self->{'count'}";
return (1);
}
$self->{'data'} = $self->{'count'};
if ($LIMIT > $self->{'count'}) {
$self->{'count'}++;
print "FALSE\n";
return (0);
} else {
$self->{'state'} = 'done';
$self->{'msg'} = 'normal termination';
print "TRUE\n";
return (1);
}
}
package main;
##
#
# now for the simulation. we fill a list with numbers, then
# iterate using that list as a queue. items that fail with
# recoverable errors get pushed back on the queue for another
# try, and items with fatal errors get dropped.
#
# the real point of this whole mess is to see the tracking
# statements for each pass through the loop. you can see
# the order in which functions are called, and the TRUE/FALSE
# results that go back to the range operator each step of the
# way. you'll see a TRUE (FALSE)+ TRUE FALSE sequence when
# everything works, and the range operator maps that to the
# sequence (TRUE)+ FALSE.
#
##
@list = (1..10);
while (@list) {
$i = shift @list;
print "======== trying $i\n\n";
## create a control object and run the loop
@cache = ();
$obj = new Loop_control;
while ($obj->updown .. $obj->advance) {
push @cache, $obj->{'data'} * $i;
}
## then decide what to do with the results
print "\n## $obj->{'msg'}: ";
if ($obj->{'state'} eq 'done') {
print join (', ', @cache), "\n\n";
} elsif ($obj->{'state'} eq 'error') {
print "recoverable. re-queueing $i\n\n";
push @list, $i;
} else {
print "fatal error. giving up on $i\n\n";
}
print "======== end $i\n\n";
}