next: Libraries previous: Structures top: Contents
Exceptions
An exception is an event which occurs during the execution of a program that disrupts the normal flow of the program. The runtime raises an exception event when it does not know what to do in a particular situation. Division by zero is one such situation.
> (/ 10 0) /: division by zero
An exception event is wrapped up in an object of the structure type exn. The string representation of the exception can be retrieved by using the exn-message procedure.
Usually when an exception occurs, the evaluation of the expression stops and the program returns to the top-level, where the exception is handled. The toplevel will print the error message and go back to the input mode. The interpreter will print the error message to the console and exit.
Spark provides a construct to help programmers handle an exception event, without exiting to the top-level. This is the try-catch construct. If an expression in the try block raises an exception, the catch construct will handle it. catch takes a procedure as argument. The exception object is passed to this procedure.
(try
(/ 10 0)
(catch
(lambda (exception)
(cond
((exn? exception)
(print (exn-message exception)))
(else
(print exception))))))
Notice that the exception handler procedure checks the type of the exception before it decides how to deal with it. This is because all exceptions need not be objects of exn.
There are two procedures that can be used to raise an exception event - raise and error. raise takes one argument which can be any object. This is passed to the event handler procedure of catch. The procedure error raises the exception exn:fail (which contains an error string). The error procedure has three forms:
-
(error symbol) creates a message string by concatenating "error: " with the string form of symbol.
-
(error msg-string v ...) creates a message string by concatenating msg-string with string versions of the vs. A space is inserted before each v.
-
(error src-symbol format-string v ...) creates a message string equivalent to the string created by:
(format (string-append "error: " format-string) src-symbol v ...ยท)
The finally clause
The try-catch construct can have an additional clause called finally which takes a procedure as argument. This procedure is executed, irrespective of whether there was an exception or not. Let us study the finally clause with the help of a simple example. Imagine a computer with a file system, where all files are represented by numbers. User programs can open any file with an id greater than 4 and read from it. If an attempt is made to read from files 0 - 4, an exception is raised. Here is the implementation of this file system:
;; Returns a random file id.
(define (open-file)
(random 10))
;; Tries to 'read' text from a file
;; whose id is >= 5.
(define (read-from-file file-id)
(if (< file-id 5)
(raise "Don't have permission to write to file.")
"hello, world"))
;; Closes the file.
(define (close-file file-id)
#t)
A file that is opened with open-file must be closed with a corresponding call to close-file to avoid resource leaks. The following test procedure opens a file, reads from it and closes it. Note that the call to close-file is within the finally clause. This makes sure that the file is closed under any circumstance.
(define (test-file-io)
(let ((file-id (open-file)))
(try
(printf "~a~n" (read-from-file file-id))
(catch (lambda (ex)
(printf "Error: ~a (~a)~n" ex file-id)))
(finally (lambda ()
(close-file file-id)
(printf "File ~a closed.~n" file-id))))))
Continuations
A continuation is a point in an expression being evaluated, frozen in time. Consider the following code:
(+ 2 3) ;; => 5
This expression can be split into three points: +, 2 and 3. We can freeze the program at any of these points and store it away. This is accomplished with the procedure call-with-current-continuation or call/cc. The name itself gives a hint as to what it does. call/cc takes a procedure as argument and evaluates it. The evaluated procedure is passed a closure which contains the expression up to where call/cc was called.
Let us see call/cc in action by freezing the point where the value 3 is supplied:
(define frozen #f) (+ 2 (call/cc (lambda (k) (set! frozen k) 3))) ;; => 5
The current continuation is wrapped into the procedure object k. We store this in the global variable frozen and return the value 3 as required by the original expression. So, when it is evaluated the first time, the result will be 5.
An easy way to understand continuations is to look at the expression as consisting of the procedure '+' and two holes:
(+ [] [])
The first hole is filled by the value 2:
(+ 2 [])
The second hole is filled by whatever call/cc evaluates to (in this case 3):
(+ 2 3) ;; => 5
The expression with the hole is stored in the continuation object. Anytime in the future, we can reuse this object like this:
(frozen 10) ;; => 12
What really happened when the above expression was evaluated? The expression (+ 2 []) was resurrected and the hole was filled by the value returned by frozen. Thus the expression becomes (+ 2 10) and gets evaluated to 12.
If you are a C programmer, continuations could be understood in the context of the setjmp and longjmp functions. While C allows you to jump back up the stack, call/cc allows moving in any direction. This is achieved by saving the entire stack at the point where call/cc was called. In our example, frozen is actually a closure that stores the stack up to the point where call/cc was evaluated.
One common use of continuations is in emulating keywords that provide 'escape' from a context. In the C family of languages these keywords are return, break and continue. Let us see how we can emulate one of them - the return keyword:
;; This procedure searches a list for a value.
;; If the value is found, the procedure exits by
;; ''returning'' that value.
(define (find-element list-to-search is?)
(call/cc
(lambda (return)
(for element in list-to-search
(if (is? element)
(return element)))
#f)))
;; Test
(define (is-1900? e)
(= e 1900))
(find-element (list 1890 2009 1900 2001) is-1900?) ;; => 1900
(find-element (list 1890 2009 1901 2001) is-1900?) ;; => #f
The definition of find-element can be visualized as:
(define (find-element list-to-search is?)
[])
When the searched element is found, it is passed as the argument of return, which actually represents the point where the evaluation of the body of find-element starts. Each time find-element is evaluated, the hole is filled with either #f or the argument passed to the inner call to return. This creates an illusion that find-element actually returns a value, while in effect the entire body of find-element (the hole) is getting replaced (or filled) by the value to which the continuation procedure evaluates.
There is a built-in form that makes it easy to create escape continuations called let/ec. Here is the find-element procedure re-written using let/ec:
(define (find-element list-to-search is?)
(let/ec return
(for element in list-to-search
(if (is? element)
(return element)))
#f))
We can also use let/ec to emulate the break keyword, which can be used to terminate a loop prematurely:
(define i 0)
;; This loop prints up to 5
;; and breaks.
(let/ec break
(while (< i 10)
(printf "~a~n" i)
(if (= i 5)
(break #f))
(set! i (add1 i))))
Continuations find use in implementing features like exception handling, green threads, co-routines etc. Most of these are already taken care of by abstractions built into Spark, so the programmer is saved from directly dealing with continuations. But understanding how continuations work is important because it can be used to provide intuitive solutions to certain kind of problems.
Some links that provide more information on continuations:
1.
http://vmathew.in/articles/yaaec.html
2.
http://community.schemewiki.org/?call-with-current-continuation
3.
http://en.wikipedia.org/wiki/Continuation
4.
http://www.ps.uni-sb.de/~duchier/python/continuations.html (Examples in Python)
5.
http://www.ibm.com/developerworks/library/j-contin.html (Examples in Java)
