Issue 18
0. Introduction
Welcome to issue 18 of the Chicken Gazette.
1. The Hatching Farm
The last two weeks have been busy times in our egg repository. The following eggs have been updated:
- Peter Bex
- epeg released v2.4
- imlib released v0.13
- slatex released 20090928.1
- wmiirc released 0.5
- svn-client released v0.16
- Thomas Chust
- openssl released version 1.5.1
- Moritz Heidkamp
- remote-mailbox-threads released version 0.0.1
- char-set-literals tag version 0.1
- pool tag version 0.2
- hyde tag version 0.14
- Kon Lovett
- bloom-filter released 1.1.1
- Ivan Raikov
- flsim has initial support for Octave code generation now
- Jim Ursetto
- sql-de-light released 0.4.3
- Felix Winkelmann
- unix-sockets tagged v1.5
- bind released 0.93^W0.94
- coops released v1.3
New eggs in our farm:
- Moritz created validator
- Nick Gasson released Slime v1.0, now that it is in an egg, you don't need to go through the git install hoop to get it. Thanks Nick!
Egg tickets fixed:
- Ticket #480 utf8 egg noop->void in experimental branch.
- Ticket #476 unix-sockets missing (require-library posix)
- Ticket #477 unix-sockets using obsolete pointer type
- Ticket #471 (message-digest tests fail
- Ticket #470 locale egg should rename getenv calls
- Ticket #468 MAGIC-LIMIT is undefined in lookup-table
Ongoing stuff
- Peter Bex worked hard on crypt the last weeks
- Alan Post put tremendous effort into bug fixes in genturfahi.
- Alaric Snell-Pym worked on , adding as he says "...just a few more unit tests. [P]rogress is slow but steady though."
2. Core development
We part from the following bugs:
- Ticket #484 -scrutinize not properly expanding match macro, reported by Alan Post.
- Ticket #479 bind egg should support ___blob
- Ticket #466 csc -gui broken (OS X)
But also the core has been busy:
CR439 has been added by Peter Bex as voting time is up and no one spoke (against it).
A Bug related ##sys#unblock-threads-for-i/ has been reported by Jim Ursetto and fixed in experimental.
A new function called condition->list has been added to the library. This allows conversion and handling of conditions without knowledge of the inner structure of conditions.
Of course some entries have been missing again from the types.db, reported by Kon Lovett.
(compile-file) now returns #f on error.
The default trace-buffer size is now 16.
Sven Hartrumpf provided a patch to the make-dist helper scripts fixing the deprecated getenv procedure, the new one is named get-environment-variable.
__byte_vector has been marked deprecated. __blob has been added to bind.
The -gui option has been fixed, as it tried to link with chicken.rc.o on all platforms. Thanks to ddp for reporting that.
Taylor Venable has reported serious breakage in the accumulated profiling code, thanks!
Christian Kellermann spotted a problem in the newly changed equal? patch.
Kon also spotted inefficient type checks for the 64 bit integers. Felix fixed it, then Sven provided a patch for broken integer64 type checks.
Felix Winkelmann appeased some compiler warnings in the runtime code, apart from being busy applying all the patches :)
Jules Altfas spotted that vector-copy! was missing from types.db. Good catch!
3. Chicken Talk
Christian Kellermann announced that he has set up a salmonella instance for Chicken's experimental branch on his server. If you are an egg maintainer and would like to know whether your eggs will break with the upcoming Chicken, check out the reports. Christian is going to move the installation over to call-cc.org once the configuration has stabilized.
David Dreisigmeyer had a nice surprise for the mailing list last week. After asking a few suspicious questions about embedding Chicken he came out with a Chicken REPL embedded in a Python REPL. Now that's nice!
Sandro Schugk kicked off a discussion about the type relationship of lists and pairs, featuring insightful comparisons with Common Lisp's view of this affair as well as an ASCII art type diagram by Thomas Chust.
Vilson Vieira found a shortcoming in bind's typedef parsing. Felix Winkelmann announced to implement the missing functionality as soon as he gets around to it. Judging by Vilson's site we can probably hold our breaths for something really cool to come out of this!
Finally, Alan Post (again) uncovered a problem while working on his infamous parser genturfahi. It turned out to be caused by a bug in matchable's recently added tail-pattern support. Alex Shinn has promised to fix it as soon as possible. Thanks Alan for shaking out yet another bug and thanks Alex for your continued great job on your Scheme libraries!
Meanwhile elsewhere on the internets... Mario has found these gems:
Vilson Vieira started coding Chicken bindings to PortAudio in his github repo. He's got some help from Moritz via #chicken.
On a reddit thread, Chris Targett mentioned he started binding inotify to Chicken. Chris has also got his repository of Chicken stuff on github.
After seeing a very silly benchmark performed by Mario Domenech Goulart, Bradley Lucier started a thread on Gambit's mailing list showing some results which indicate that "Chicken really smokes Gambit" on this femtobenchmark. The question is, should it?" Marc Feeley follows up with the reassuring statement: ''The x86 back-end runs fib_scm about 2.5 times faster than when using the C back-end. That's faster than Chicken, and also Larceny and Ikarus (which have x86 back-ends). The Gambit x86 back-end supports both x86-32 and x86-64.'' So we guess all is well again.
C'esar L. B. Silveira translated the C code for tinywm to Chicken. The resulting code can be found at http://cesarbs.notapipe.org/tinywm-scheme.html.
4. Omelette Recipes
Today I will introduce you to a much asked for topic of Chicken Scheme on our IRC channel last week: Exception handling in Chicken Scheme.
These are our conditions
A newsgroup posting once had this quote:
To err is human to restart divine
which relates to Common Lisp being able to do so called restarts on error conditions. In Chicken Scheme there are no restarts but it does come with a condition system that allows programmers to recover from exceptional conditions. Before I start, let me introduce a bit of vocabulary:
- exception
- A situation that needs special attention. Often used synonymously with condition.
- condition
- An object representing a current exceptional event.
- exception-handler
- A procedure that gets called with a condition object
- signal
- A procedure that "raises" a condition by invoking the condition-handler with a condition object.
- abort
- A procedure that also raises a condition but ensures that the condition handler cannot return without raising another error. See below.
Conditions should be raised whenever the program encounters a situation where it cannot go on without interaction with the calling code. Typical situations are: Operations on closed file descriptors, broken sockets, non existing files, memory allocation failures, syntax or type errors.
As some of these conditions might be corrected in the handler and then be allowed to continue, Chicken Scheme's condition system distinguishes between two types of conditions: continuable and non-continuable conditions. As the name suggests non-continuable conditions are such severe events that the code cannot possibly continue at the location where the condition has been raised. Type errors fall in this category, i/o errors as well as others. Continuable conditions on the other hand can continue where they have been signaled if the handler decides to do so. It may be possible to correct the error in the handler and have the code go along with it.
Condition objects
A condition object is a distinct data type that consists of a condition kind and of property / value pairs. These are also called property-conditions and therefore the constructing procedure is
(make-property-condition kind-key prop-key value ...)
Where kind-key and prop-key are symbols. The constructor accepts any number of key / value pairs. The most basic condition signalled by the Chicken Scheme system is the exn condition kind. All exn conditions have three properties:
- message
- Textual message explaining the error.
- arguments
- The arguments passed to the condition handler.
- location
- The name of the procedure which raised the condition.
To access properties in your code there are a couple of helper procedures defined in SRFI-12: condition-predicate, condition-property-accessor and get-condition-property. The condition-predicate procedure generates a predicate function that can be used to decide whether the condition kind is interesting to you:
(let ((exn (make-property-condition 'all-my-fault 'message "I have failed, master"))) (if ((condition-predicate 'all-my-fault) exn) (print "He has failed again!") (print "Something else is rotten."))) ;;; Prints "He has failed again!"
The condition-property-accessor allows us to inspect the exception a bit more closely if we know what we are looking for:
(let ((exn (make-property-condition 'all-my-fault 'message "I have failed, master"))) (if ((condition-predicate 'all-my-fault) exn) (print ((condition-property-accessor 'all-my-fault 'message "No message.") exn)) (print "Something else is rotten."))) ;;; Prints "I have failed, master!"
Note that the "No message" argument is an extension to the SRFI-12 procedure. It will serve as a default in case your exception does not provide the property you've asked for. If you omit the default and the property is not available an error will be raised (yes, another exception).
An easier form of condition-property-accessor is get-condition-property which corresponds to it like this:
(define (get-condition-property EXN KIND PROPERTY [DEFAULT])
((condition-property-accessor KIND PROPERTY [DEFAULT]) EXN))
So it can save you a bit of typing parentheses.
To be able to generate more complex condition objects it is possible to combine conditions to create so-called composite-conditions. The constructing procedure is called
(make-composite-condition condition ...)
and accepts property conditions objects and combines their kind keys together in the order of the arguments. Properties are associated with their conditions, meaning that more conditions can have the same property names and those will not get mixed up. The Chicken Scheme system provides these possible exceptions:
- (exn arity)
- Signaled when a procedure is called with the wrong number of arguments.
- (exn type)
- Signaled on type-mismatch errors, for example when an argument of the wrong type is passed to a built-in procedure.
- (exn arithmetic)
- Signaled on arithmetic errors, like division by zero.
- (exn i/o)
- Signaled on input/output errors.
- (exn i/o file)
- Signaled on file-related errors.
- (exn i/o net)
- Signaled on network errors.
- (exn bounds)
- Signaled on errors caused by accessing non-existent elements of a collection.
- (exn runtime)
- Signaled on low-level runtime-system error-situations.
- (exn runtime limit)
- Signaled when an internal limit is exceeded (like running out of memory).
- (exn match)
- Signaled on errors raised by failed matches (see the section on match).
- (exn syntax)
- Signaled on syntax errors.
These are also the system conditions raised by the Chicken Scheme conditions system.
Now let's have a look what handlers look like.
Condition handling forms
The exception-handler that is in charge at the time of rise of the exception is determined by the parameter
(current-exception-handler procedure)
which holds a procedure with unary arity expecting the condition object as argument. SRFI-12, the enhancement document to R5RS that Chicken Scheme took its condition system from suggests three forms that can implement condition handling. From lower to higher level they are:
(with-exception-handler handler thunk)
Sets the current-exception-handler to handler for the invocation of thunk and resets it afterwards. Warning: This procedure is very low level and should not be used in most cases, as you can create infinite loops with it.
(handle-exceptions var handle-expression expr1 expr2 ...)
This macro calls all the expressions with a special exception handler installed:
((call-with-current-continuation
(lambda (k)
(with-exception-handler
(lambda (var)
(k (lambda ()
handle-expression)))
(lambda ()
(call-with-values
(lambda() expr1....)
(lambda args
(k (lambda () (apply values args))))))))))
This is a full expansion of the handle-exceptions macro. It transforms your code into a ((call/cc ..)) block, which will cause evaluation of itself later. As you can see it fetches the current continuation of the macro first to continue where it left off. Then it builds the exception handling function which binds the condition object to the name var as specified in the macro and calls the continuation k with the result of handle-expression.
The expressions are called in order, returning the result of the last expression.
(condition-case expression clause ...)
The most high level of the three. It allows running expression (a thunk) and then deal with the condition objects in a case like manner. Each clause has the form
([variable] (kind1 kind2 ...) BODY ...)
Where the optional variable provides a binding to the condition object in BODY. The kind clause is matched against the kind key of the condition object. So to simply catch exn conditions have a clause ((exn) (display "Oooh an exn condition!")), to filter out a file exception use ''((exn file) (display "I see, something's wrong with a file!")). The clauses are, like cond, matched in the order of appearance so be sure to place more special clauses first, general clauses next and an catch-all exn ()'' clause (optionally) last. If the catch all clause is omitted the exception is re-raised by condition-case.
Because condition-case captures the continuation just as 'handle-exception' does, re-raising the condition causes this continuation's exception handler to get called, so that the unhandled condition is handed over to the next handler. If no handler does handle the exception, the toplevel handler will finally abort the program and prints a call chain as well as the exn properties OR an unhandled exception message.
How to summon the beast
Now that we know how to handle exceptions we would like to raise them ourselves. There are two basic procedures that raise conditions, signal and abort. Signal will be the most commonly used procedure for raising an exception. It's signature looks like
(signal condition-object)
and it calls (current-exception-handler) with condition-object. The other procedure abort does a little bit more than that: It calls the current-condition-handler with the condition-object then when the handler returns it calls itself with a condition object signaling that the handler returned. Why?
To continue or not continue -- That's the question
As I wrote above there are now two ways to handle the conditions as they occur. The handler can decide to return any value it chooses or it can jump off somewhere else for example back to the top level of the REPL printing a call chain and the error message. This behavior is implemented in the primordial condition handler as it is installed by the Chicken Scheme system from the start.
Depending on the application it might be also sensible to continue the computation with a corrected value instead of halting the process altogether.
This different treatment after a condition is raised distinguishes continuable conditions (that continue in the handler) from non-continuable conditions that have to do something else, usually aborting the current computation.
So with this distinction in mind it becomes clear that signal should be used for raising continuable exceptions whereas abort should be used to raise non-continuable exceptions.
Making continuable exceptions
However one needs to be careful with constructing continuable exceptions for a couple of reasons:
You need to do it yourself. Since the higher level constructs handle-exceptions as well as condition-case will always jump to the continuation where they have been called themselves, you will always jump "out" of your desired expression.
Abort will call the handler again. Whenever in your code some system exception is raised with abort you will end up in an infinite loop. This code for example does loop infinitely: (with-exception-handler (lambda (e) 1) (lambda () (+ 1 (/ 1 0)))) Despite the author's intention to just carry on with a bogus result this will call abort, which then will call the (current-exception-handler) again as we have seen.
A cure for this would be to check if it is an exception you are expecting (from your own code) or an exception raised by someone else which could mean jumping out to the old handler:
(define (handle-input i) (if (even? i) (signal (make-property-condition 'my-condition 'message "Wrong input" 'arguments (list i))) i)) (define (test l) (let ((old-handler (current-exception-handler))) (with-exception-handler (lambda (exn) (cond (((condition-predicate 'my-condition) exn) (printf "called.~%~!") #f) (else (old-handler exn)))) (lambda () (map handle-input l)))))
The results are
#;1> (test '(1 2 3 4)) called. called. (1 #f 3 #f) #;2> (test '(1 2 y a)) called. Error: (even?) bad argument type: y Call history: <eval> [test] (map handle-input l) <eval> [handle-input] (even? i) <eval> [handle-input] (even? i) <eval> [handle-input] (signal (make-property-condition (quote my-condition) ... <eval> [handle-input] (make-property-condition (quote my-condition) ... <eval> [handle-input] (list i) <eval> [test] ((condition-predicate (quote my-condition)) exn) <eval> [test] (condition-predicate (quote my-condition)) <eval> [test] (printf "called.~%~!") <eval> [handle-input] (even? i) <--
Non-continuable exceptions or business as usual
Non-continuable exceptions really are easier to understand due to their 1:1 mapping to other languages with exception systems. They come close to the Java try {} catch (Exception e){} block.
From the lessons learned above, always use condition-case if you just want to handle your exceptional state.
An example with handle-exceptions (from the SRFI-12 docs):
(define (try-car v) (let ((orig (current-exception-handler))) (with-exception-handler (lambda (exn) (orig (make-composite-condition (make-property-condition 'not-a-pair 'value v) exn))) (lambda () (car v))))) (try-car '(1)) ;=> 1 (handle-exceptions exn (if ((condition-predicate 'not-a-pair) exn) (begin (display "Not a pair: ") (display ((condition-property-accessor 'not-a-pair 'value) exn)) (newline)) (ABORT exn)) (try-car 0)) ; displays "Not a pair: 0"
And with condition-case:
(define (check thunk) (condition-case (thunk) [(exn file) (print "file error")] [(exn) (print "other error")] [var () (print "something else")] ) ) (check (lambda () (open-input-file ""))) ; -> "file error" (check (lambda () some-unbound-variable)) ; -> "othererror" (check (lambda () (signal 99))) ; -> "something else" (condition-case some-unbound-variable ((exn file) (print "ignored")) ) ; -> signals error
In my opinion condition-case makes it easier for 90% of your programming needs to catch explicitly certain types of exceptions in your code. If you need continuable exceptions you may resort to a more manual approach as outlined above.
Conclusion
I hope I have been able to shed some light on the usage of exceptions in Chicken Scheme. I am grateful for any kind of comments and criticism to this piece. In the long term a merge of the results of this experimental article and the SRFI-12 docs in Chicken could benefit all.
I am also thankful to Alaric, Felix and Peter who have read through drafts of this article.
So long, may your code be exceptionally well guarded from now on.
5. About the Chicken Gazette
The Gazette is produced bi-weekly by a volunteer from the Chicken community. The latest issue can be found at http://gazette.call-cc.org or you can follow it in your feed reader at http://gazette.call-cc.org/feed.atom. If you'd like to write an issue, consult the wiki for the schedule and instructions!