A tale of restarts

624 views
Skip to first unread message

Chris Double

unread,
Apr 23, 2000, 3:00:00 AM4/23/00
to
This is a "I'm glad I used Dylan" story...

I wrote a program to download a few thousand web pages containing
information I needed, parse the pages for that information, and store
it in an ODBC database for later querying, data mining, etc.

The information on the pages had to be cross referenced with existing
database tables to ensure validity of data, making sure foreign keys
with code tables, dates, etc matched. I took this as a good opportunity
to use Dylan restarts and exceptions.

In the program I made sure exceptions were thrown whenever data from
the web pages did not match expected information in the database. For
each exception I created a restart protocol and made sure I had restart
handlers to allow correcting of the exceptions. Some of the exceptions
could be programatically fixed - so I had handlers that did this. An
example was where a lookup on a person's name failed. I had a handler
that tried soundex checking on the name to find a match and retry if
there was one with the new name.

There were a few cases where I couldn't have the program handle it or I
didn't think it worth it, so I specialised 'restart-query' on the
restart class to prompt for the correct information and made sure I had
a restart handler where the exception was signalled.

The program had been running for 10 hours and working fine. The
database was being populated and the automatic correction of exceptions
via the handlers worked well. Then up popped the Dylan exception error
message (in Functional Developer) saying that an exception had occurred
- no such record existed for a given date. Damn.

But in the list of available restarts in the Functional Developer debug
box, was the ability to choose a restart handler from those I'd made
available. Nicely printed in the list was the restart handlers I had in
scope:

- Enter data for missing record.
- Enter new date for current record.

I chose 'enter data for missing record', and my 'restart-query' method
was called prompting me to manually enter the missing data. I did this
and the program continued, completing two hours later.

The ability to create restart protocols and be able to choose them at
exception time, and prompt the user for correct values saved me *heaps*
of time. The ease of creating the restart mechanisms was a pleasant
surprise too.

Chris.
--
http://www.double.co.nz/dylan

David McClain

unread,
Apr 23, 2000, 3:00:00 AM4/23/00
to
How about a quickie example of code to illustrate your technique?

- DM

Chris Double <ch...@double.co.nz> wrote in message
news:8F202465chr...@202.27.184.33...

Chris Double

unread,
Apr 24, 2000, 3:00:00 AM4/24/00
to
"David McClain" <dmcc...@azstarnet.com> writes:

> How about a quickie example of code to illustrate your technique?

Here is a quick example. One of the items on the web page is a city
name. I need to look up the database using the name and get and ID
number for it. Sometimes the city name is mis-spelled on the web page
and so the ID is not found. It was a console program so the following
using console input/output. This was typed in and cut down from the
original so may contain syntax errors but it shows the general idea of
what I used.

---------------------8<------------------------
// Exception and restart protocols
//
// Any method that signals the <no-city-id-found> exception
// can be recovered from using two restart protocols.
// A handler for <no-city-found> can signal <use-city-id-restart>
// to return a specific ID to the method that tried to find the
// ID originally.
//
// Alternatively, <try-city-name-restart> can be signalled to
// retry the method call using a different name (different spelling
// for example).
//
define class <no-city-id-found> (<error>)
slot error-city-name :: <string>, init-keyword: name:;
end class;

define class <use-city-id-restart> (<restart>)
slot restart-city-id :: <integer>, init-keyword: id:;
end class;

define method restart-query( restart :: <use-city-id-restart>)
format-out("Enter city id to use: \n");
let id-as-string = read-line(*standard-input*);
restart.restart-city-id := string-to-integer(id-as-string);
end method restart-query;

define class <try-city-name-restart> (<restart>)
slot restart-city-name :: <string>, init-keyword: name:;
end class;

define method restart-query( restart :: <try-city-name-restart>)
format-out("Enter city name to use: \n");
let name = read-line(*standard-input*);
restart.restart-city-name := name;
end method restart-query;

// Method used to return an ID given a city name
define method get-city-id(name)
block(return)
// Provides the action to perform if a restart provides
// an explicit ID to use.
let handler (<use-city-id-restart>
init-arguments: vector(
format-string: "Enter city id for %s.",
format-arguments: vector(name))) =
method(condition, next-handler)
return(condition.restart-city-id)
end method;

// A restart handler that tries again using a different
// city name (perhaps there was a mis-spelling with the
// original attempt).
let handler (<use-city-name-restart>
init-arguments: vector(
format-string: "Enter city name for %s.",
format-arguments: vector(name))) =
method(condition, next-handler)
// Tries method again with new name
return(get-city-id(condition.restart-city-name));
end method;

// Retrieve an id given the name using sql-odbc
let query = format-to-string(
"select city_id from cities where city_name='%s'",
name);

let result = execute(query);
if(result & result.size > 0)
first(first(result)); // retrieves id from result rows
else
error(make(<no-city-id-found>, name: name));
end if;
end block;
end method;

// Test caller for above method - one error results
// allowing the user to manually restart (see text at end).
define method foo()
get-city-id("Wellington");

get-city-id("Auckand"); // *** (A) ***
end method;

// Call get-city-id, but have an exception handler
// to automatically restart.
define method bar()
let handler (<no-city-id-found>) =
method(condition, next-handler)
// Oops, no city - get an alternative spelling.
// by replacing all 'Mt' with 'Mt.'.
let original-name = condition.error-city-name;
let new-name = replace-string(original-name, "Mt ", "Mt. ");
if(new-name ~= original-name)
// Cause the restart with the new name.
error(make(<use-city-name-restart>, name: new-name));
else
next-handler();
end;
end method;

// Sometimes in the original web pages as Mt. Cook, Mt Cook, etc.
// The below is not found, the exception is caught by the above
// handler, which causes a restart to try with the alternative
// name. This succeeds and the program continues. If that fails,
// then the exception is raised again, caught by the handler,
// it sees that there is no new variation of the string to try
// and passes to the next handler. The next handler (as there is
// none) passes to the FD exception dialog as per foo() example.
get-city-id("Mt Cook");
end method;
---------------------8<------------------------

In the foo() method (*** (A) *** above) the get-city-id fails because
the name is spelt wrong. There is no local handler in scope so
Function Developer throws up an dialog box explaining the error and
displaying the possible restarts. These include the descriptions
placed in the 'let handler' in the original method:

- Enter city id for Auckand
- Enter city name for Auckand

The user can choose either restart and the restart-query method for
the restart gets called, prompting for either the correct ID or an
alternative name to try.

Hope that helps explain it a bit. It's my first real foray into
exceptions and restarts so I'd be interested in other approaches. The
thing I like about the above is if I don't handle an exception, the
program can still be restarted/continued from the FD final exception
handler.

Chris.
--
http://www.double.co.nz/dylan


Reply all
Reply to author
Forward
0 new messages