[Prev][Next][Index][Thread]

Re: A tale of restarts



"David McClain" <dmcclain@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




References: