#!/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"; }