December 1, 2017 10:00 AM
All programming languages provide ways to handle exceptions, and all programs should be careful to handle exceptions properly; it’s good programming practice to keep exceptions from interfering with the normal conduct of your program, and courteous to your users. A good way to handle exceptions is to replace them with default values. Consider this:
> (car '()) Exception in car: () is not a pair > (try (car '()) #f) #f
The first exception is unhandled, and displays an error to the user. The second exception is handled, and converts the error to a default value. Replacing exceptions with defaults makes programs more robust.
Your task is to devise a simple system of replacing exceptions with defaults in your favorite programming language. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.
Posted by programmingpraxis
Categories: Exercises
Tags:
Mobile Site | Full Site
Get a free blog at WordPress.com Theme: WordPress Mobile Edition by Alex King.
Below are my procedural and syntactic interpretations of the idea.
The syntactic version puts the default first, before the body of
expressions, because I thought that a bit nicer, though it is easy to
change.
(import (scheme base) (scheme write)) (define (make-try/default proc dflt) (lambda args (guard (exc (else dflt)) (apply proc args)))) (define-syntax try/default (syntax-rules () ((_ dflt body body1 ...) ((make-try/default (lambda () body body1 ...) dflt))))) (define (test) (define car/default (make-try/default car 42)) (display (map car/default '((2 3 5) (2) ()))) (newline) (display (try/default '(4 2 42) (define lst '(2 3 5)) (map car (list lst '(2) '())))) (newline)) (test)By chaw on December 1, 2017 at 3:43 PM
@chaw: Your version fails because it evaluates the default expression even when it is not needed. Here is output from my macro, at the top, and your macro/procedure, at the bottom:
Both programs correctly evaluate to the
carof the list. But your program also prints “hello” as a side-effect of evaluating the default expression, which it should not do.You can run the program at https://ideone.com/9ajhyO.
By programmingpraxis on December 1, 2017 at 7:48 PM
Here’s a solution in Python.
The default value is specified using a lambda. This provides deferred evaluation to address the issue that @programmingpraxis mentions in an earlier comment (so that the default is only evaluated when needed).
The function returns a second value that indicates whether the expression was evaluated successfully.
def trydefault(expr, default): try: output = expr() success = True except: output = default() success = False return output, success l = [] element, _ = trydefault(lambda: l[0], lambda: None) print elementOutput:
By Daniel on December 2, 2017 at 6:04 PM
I disagree that this is in any way good practice. If you are passing a non-pair to car, it should fail immediately and loudly, as something is severely wrong. Changing it to #f just postpones the evil day and makes the error harder to debug.
That said, long-running programs do need to catch exceptions so as to remain long-running, and the Nulll Object pattern (which returns an object of the correct type with no contents rather than a generic nil) is perfectly good even in Lisps.
By John Cowan on December 12, 2017 at 12:26 PM
@John: I used (car ‘()) as an example. I would not handle type errors that way in practice. The text makes clear that this is a mechanism for handling some types of exceptions, not as a way to bypass real errors. For instance, if a binary tree lookup doesn’t find an expected key, this mechanism can replace the exception with some default value so the program can continue.
By programmingpraxis on December 12, 2017 at 1:48 PM