The Kawa Scheme Language

The Kawa Scheme Language

Program structure

Boolean values

The standard boolean objects for true and false are written as #t and #f. Alternatively, they may be written #true and #false, respectively.

boolean ::= #t | #f | #true | #false

What really matters, though, are the objects that the Scheme conditional expressions (if, cond, and, or, when, unless, do) treat as true or false. The phrase “a true value” (or sometimes just “true”) means any object treated as true by the conditional expressions, and the phrase “a false value” (or “false”) means any object treated as false by the conditional expressions.

Of all the Scheme values, only #f counts as false in conditional expressions. All other Scheme values, including #t, count as true. A test-expression is an expression evaluated in this manner for whether it is true or false.

Note: There are plans to also treat as false the Java values #!null, and also all java.lang.Boolean instances for which booleanValue() return false (in addition to the special Boolean:FALSE instance which is already false). However, this is not yet implemented.

Note: Unlike some other dialects of Lisp, Scheme distinguishes #f and the empty list from each other and from the symbol nil.

Boolean constants evaluate to themselves, so they do not need to be quoted in programs.

#t       ⇒  #t
#true    ⇒  #t
#f       ⇒  #f
#false   ⇒  #f
'#f      ⇒  #f

boolean

The type of boolean values. As a type conversion, a true value is converted to #t, while a false value is converted to #f. Represented as a primitive Java boolean or kava.lang.Boolean when converted to an object.

boolean? obj

The boolean? predicate returns #t if obj is either #t or #f, and returns #f otherwise.

(boolean? #f)   ⇒  #t
(boolean? 0)    ⇒  #f
(boolean? '())  ⇒  #f

not obj

The not procedure returns #t if obj is false, and returns #f otherwise.

(not #t)         ⇒  #f
(not 3)          ⇒  #f
(not (list 3))   ⇒  #f
(not #f)         ⇒  #t
(not ’())        ⇒  #f
(not (list))     ⇒  #f
(not ’nil)       ⇒  #f

boolean=? boolean1 boolean2 boolean3 ...

Returns #t if all the arguments are booleans and all are #t or all are #f.

Conditionals

test-expression ::= expression
consequent ::= expression
alternate ::= expression

if test-expression consequent alternate

if test-expression consequent

An if expression is evaluated as follows: first, test-expression is evaluated. If it yields a true value, then consequent is evaluated and its values are returned. Otherwise alternate is evaluated and its values are returned. If test yields #f and no alternate is specified, then the result of the expression is unspecified.

(if (> 3 2) 'yes 'no)          ⇒ yes
(if (> 2 3) 'yes 'no)          ⇒ no
(if (> 3 2)
    (- 3 2)
    (+ 3 2))                   ⇒ 1
(if #f #f)                     ⇒ unspecified

The consequent and alternate expressions are in tail context if the if expression itself is.

cond cond-clause^+

cond cond-clause^* (else expression)

cond-clause ::= (test-expression expression^*)
    | (test => expression)

A cond expression is evaluated by evaluating the test-expressions of successive cond-clauses in order until one of them evaluates to a true value. When a test-expression evaluates to a true value, then the remaining expressions in its cond-clause are evaluated in order, and the results of the last expression in the cond-clause are returned as the results of the entire cond expression. If the selected cond-clause contains only the test-expression and no expressions, then the value of the test-expression is returned as the result. If the selected cond-clause uses the => alternate form, then the expression is evaluated. Its value must be a procedure. This procedure should accept one argument; it is called on the value of the test-expression and the values returned by this procedure are returned by the cond expression.

If all test-expressions evaluate to #f, and there is no else clause, then the conditional expression returns unspecified values; if there is an else clause, then its expressions are evaluated, and the values of the last one are returned.

(cond ((> 3 2) 'greater)
      ((< 3 2) 'less))         ⇒ greater

(cond ((> 3 3) 'greater)
      ((< 3 3) 'less)
      (else 'equal))           ⇒ equal

(cond ('(1 2 3) => cadr)
      (else #f))               ⇒ 2

For a cond-clause of one of the following forms:

(test expression^*)
(else expression expression^*)

the last expression is in tail context if the cond form itself is. For a cond clause of the form:

(test => expression)

the (implied) call to the procedure that results from the evaluation of expression is in a tail context if the cond form itself is.

case case-key case-clause^+

case case-key case-clause^* case-else-clause

case-key ::= expression
case-clause ::= ((datum^*) expression^+)
    | ((datum^*) => expression)
case-else-clause ::= (else  expression^+)
    | (else => expression)

Each datum is an external representation of some object. Each datum in the entire case expression should be distinct.

A case expression is evaluated as follows.

  1. The case-key is evaluated and its result is compared using eqv? against the data represented by the datums of each case-clause in turn, proceeding in order from left to right through the set of clauses.

  2. If the result of evaluating case-key is equivalent to a datum of a case-clause, the corresponding expressions are evaluated from left to right and the results of the last expression in the case-clause are returned as the results of the case expression. Otherwise, the comparison process continues.

  3. If the result of evaluating key is different from every datum in each set, then if there is an case-else-clause its expressions are evaluated and the results of the last are the results of the case expression; otherwise the result of case expression is unspecified.

If the selected case-clause or case-else-clause uses the => alternate form, then the expression is evaluated. It is an error if its value is not a procedure accepting one argument. This procedure is then called on the value of the key and the values returned by this procedure are returned by the case expression.

(case (* 2 3)
  ((2 3 5 7) 'prime)
  ((1 4 6 8 9) 'composite))    ⇒ composite
(case (car '(c d))
  ((a) 'a)
  ((b) 'b))                    ⇒ unspecified
(case (car '(c d))
  ((a e i o u) 'vowel)
  ((w y) 'semivowel)
  (else => (lambda (x) x)))    ⇒ c

The last expression of a case clause is in tail context if the case expression itself is.

and test-expression

If there are no test-expressions, #t is returned. Otherwise, the test-expression are evaluated from left to right until a test-expression returns #f or the last test-expression is reached. In the former case, the and expression returns #f without evaluating the remaining expressions. In the latter case, the last expression is evaluated and its values are returned.

(and (= 2 2) (> 2 1))          ⇒  #t
(and (= 2 2) (< 2 1))          ⇒  #f
(and 1 2 'c '(f g))            ⇒  (f g)
(and)                          ⇒  #t

The and keyword could be defined in terms of if using syntax-rules as follows:

(define-syntax and
  (syntax-rules ()
    ((and) #t)
    ((and test) test)
    ((and test1 test2 ...)
     (if test1 (and test2 ...) #t))))

The last test-expression is in tail context if the and expression itself is.

or test-expression

If there are no test-expressions, #f is returned. Otherwise, the test-expressions are evaluated from left to right until a test-expression returns a true value val or the last test-expression is reached. In the former case, the or expression returns val without evaluating the remaining expressions. In the latter case, the last expression is evaluated and its values are returned.

(or (= 2 2) (> 2 1))           ⇒ #t
(or (= 2 2) (< 2 1))           ⇒ #t
(or #f #f #f)                  ⇒ #f
(or '(b c) (/ 3 0))            ⇒ (b c)

The or keyword could be defined in terms of if using syntax-rules as follows:

(define-syntax or
  (syntax-rules ()
    ((or) #f)
    ((or test) test)
    ((or test1 test2 ...)
     (let ((x test1))
       (if x x (or test2 ...))))))

The last test-expression is in tail context if the or expression itself is.

when test-expression form...

If test-expression is true, evaluate each form in order, returning the value of the last one.

unless test-expression form...

If test-expression is false, evaluate each form in order, returning the value of the last one.

Definitions

define name [:: type] value

In addition to define (which can take an optional type specifier), Kawa has some extra definition forms.

define-private name [:: type] value

define-private (name formals) body

Same as define, except that name is not exported.

define-constant name [:: type] value

define-early-constant name [:: type] value

Defines name to have the given value. The value is readonly, and you cannot assign to it. (This is not fully enforced.)

If define-early-constant is used or the value is a compile-time constant, then the compiler will create a final field with the given name and type, and evaluate value in the module's class initializer (if the definition is static) or constructor (if the definition is non-static), before other definitions and expressions. Othewise, the value is evaluated in the module body where it appears.

If the value is a compile-time constant, then the definition defaults to being static.

define-variable name [init]

If init is specified and name does not have a global variable binding, then init is evaluated, and name bound to the result. Otherwise, the value bound to name does not change. (Note that init is not evaluated if name does have a global variable binding.)

Also, declares to the compiler that name will be looked up in the dynamic environment. This can be useful for shutting up warnings from --warn-undefined-variable.

This is similar to the Common Lisp defvar form. However, the Kawa version is (currently) only allowed at module level.

For define-namespace and define-private-namespace see the section called “Namespaces and compound symbols”.

Local binding constructs

The binding constructs let, let*, letrec, and letrec* give Scheme a block structure, like Algol 60. The syntax of these four constructs is identical, but they differ in the regions they establish for their variable bindings. In a let expression, the initial values are computed before any of the variables become bound; in a let* expression, the bindings and evaluations are performed sequentially; while in letrec and letrec* expressions, all the bindings are in effect while their initial values are being computed, thus allowing mutually recursive definitions.

let ((variable [:: type] init) ...) body

Declare new local variables with the given name, initial value init, and optional type specification type. The inits are evaluated in the current environment (in left-to-right onder), the variables are bound to fresh locations holding the results, the body is evaluated in the extended environment, and the values of the last expression of body are returned. Each binding of a variable has body as its region. If type is specified, then after the expression init is evaluated, the result coerced to type, and then assigned to the variable. If type is not specified, it defaults to Object.

(let ((x 2) (y 3))
  (* x y)) ⇒ 6
(let ((x 2) (y 3))
  (let ((x 7)
        (z (+ x y)))
    (* z x)))   ⇒ 35

let* ((variable [:: type] init) ...) body

The let* binding construct is similar to let, but the bindings are performed sequentially from left to right, and the region of a variable is that part of the let* expression to the right of the binding. Thus the second binding is done in an environment in which the first binding is visible, and so on. The variables need not be distinct.

(let ((x 2) (y 3))
  (let* ((x 7)
         (z (+ x y)))
    (* z x)))  ⇒ 70

letrec ((variable [:: type] init) ...) body

letrec* ((variable [:: type] init) ...) body

The variables are bound to fresh locations, each variable is assigned in left-to-right order to the result of the corresponding init, the body is evaluated in the resulting environment, and the values of the last expression in body are returned. Despite the left-to-right evaluation and assignment order, each binding of a variable has the entire letrec or letrec* expression as its region, making it possible to define mutually recursive procedures.

In Kawa letrec is defined as the same as letrec*. In standard Scheme the order of evaluation of the inits is undefined, as is the order of assignments. If the order matters, you should use letrec*.

If it is not possible to evaluate each init without assigning or referring to the value of the corresponding variable or the variables that follow it , it is an error.

(letrec ((even?
          (lambda (n)
            (if (zero? n)
                #t
                (odd? (- n 1)))))
         (odd?
          (lambda (n)
            (if (zero? n)
                #f
                (even? (- n 1))))))
  (even? 88))
     ⇒ #t

Lazy evaluation

Lazy evaluation (or call-by-need) delays evaluating an expression until it is actually needed; when it is evaluated, the result is saved so repeated evaluation is not needed. Lazy evaluation is a technique that can make some algorithms easier to express compactly or much more efficiently, or both. It is the normal evaluation mechanism for strict functional (side-effect-free) languages such as Haskell. However, automatic lazy evaluation is awkward to combine with side-effects such as input-output. It can also be difficult to implement lazy evaluation efficiently, as it requires more book-keeping.

Kawa, like other Schemes, uses “eager evaluation” - an expression is normally evaluated immediately, unless it is wrapped in a special form. Standard Scheme has some basic building blocks for “manual” lazy evaluation, using an explicit delay operator to indicate that an expression is to be evaluated lazily, yielding a promise, and a force function to force evaluation of a promise. This functionality is enhanced in SRFI 45, in R7RS-draft (based on SRFI 45), and SRFI 41 (lazy lists aka streams).

Kawa makes lazy evaluation easier to use, by implicit forcing: The promise is automatically evaluated (forced) when used in a context that requires a normal value, such as arithmetic needing a number. Kawa enhances lazy evaluation in other ways, including support for safe multi-threaded programming.

Delayed evaluation

delay expression

The delay construct is used together with the procedure force to implement lazy evaluation or call by need.

(delay expression) returns an object called a promise which at some point in the future may be asked (by the force procedure) to evaluate expression, and deliver the resulting value. The effect of expression returning multiple values is unspecified.

lazy expression

The lazy construct is similar to delay, but it is expected that its argument evaluates to a promise. (Kawa treats a non-promise value as if it were a forced promise.) The returned promise, when forced, will evaluate to whatever the original promise would have evaluated to if it had been forced.

eager obj

Returns a promise that when forced will return obj. It is similar to delay, but does not delay its argument; it is a procedure rather than syntax.

As a Kawa optimization, if the obj is not a promise (does not implement gnu.mapping.Lazy), it is returned as-is. (This is because Kawa generally minimizes the difference bwteen a value and forced promise evaluating to the value.)

force promise

The force procedure forces the value of promise. As a Kawa extension, if the promise is not a promise (a value that does not implement gnu.mapping.Lazy) then the argument is returned unchanged. If no value has been computed for the promise, then a value is computed and returned. The value of the promise is cached (or “memoized”) so that if it is forced a second time, the previously computed value is returned.

(force (delay (+ 1 2)))                ⇒  3

(let ((p (delay (+ 1 2))))
  (list (force p) (force p)))          ⇒  (3 3)

(define integers
  (letrec ((next
            (lambda (n)
              (cons n (delay (next (+ n 1)))))))
    (next 0)))
(define head
  (lambda (stream) (car (force stream))))
(define tail
  (lambda (stream) (cdr (force stream))))

(head (tail (tail integers)))          ⇒  2

The following example is a mechanical transformation of a lazy stream-filtering algorithm into Scheme. Each call to a constructor is wrapped in delay, and each argument passed to a deconstructor is wrapped in force. The use of (lazy ...) instead of (delay (force ...)) around the body of the procedure ensures that an ever-growing sequence of pending promises does not exhaust the heap.

(define (stream-filter p? s)
  (lazy
   (if (null? (force s))
       (delay ’())
       (let ((h (car (force s)))
             (t (cdr (force s))))
         (if (p? h)
             (delay (cons h (stream-filter p? t)))
             (stream-filter p? t))))))

(head (tail (tail (stream-filter odd? integers))))
    ⇒ 5

force* promise

Does force as many times as necessary to produce a non-promise. (A non-promise is a value that does not implement gnu.mapping.Lazy, or if it does implement gnu.mapping.Lazy then forcing the value using the getValue method yields the receiver.)

The force* function is a Kawa extension. Kawa will add implicit calls to force* in most contexts that need it, but you can also call it explicitly.

The following examples are not intended to illustrate good programming style, as delay, lazy, and force are mainly intended for programs written in the functional style. However, they do illustrate the property that only one value is computed for a promise, no matter how many times it is forced.

(define count 0)
(define p
  (delay (begin (set! count (+ count 1))
                (if (> count x)
                    count
                    (force p)))))
(define x 5)
p                  ⇒ a promise
(force p)          ⇒ 6
p                  ⇒ a promise, still
(begin (set! x 10)
       (force p))  ⇒ 6

Implicit forcing

If you pass a promise as an argument to a function like sqrt if must first be forced to a number. In general, Kawa does this automatically (implicitly) as needed, depending on the context. For example:

(+ (delay (* 3 7)) 13)   ⇒ 34

Other functions, like cons have no problems with promises, and automatic forcing would be undesirable.

Generally, implicit forcing happens for arguments that require a specific type, and does not happen for arguments that work on any type (or Object).

Implicit forcing happens for:

  • arguments to arithmetic functions;

  • the sequence and the index in indexing operations, like string-ref;

  • the operands to eqv? and equal? are forced, though the operands to eq? are not;

  • port operands to port functions;

  • the value to be emitted by a display but not the value to be emitted by a write;

  • the function in an application.

Type membership tests, such as the instance? operation, generally do not force their values.

The exact behavior for when implicit forcing is a work-in-progress: There are certainly places where implicit forcing doesn't happen while it should; there are also likely to be places where implicit forcing happens while it is undesirable.

Most Scheme implementations are such that a forced promise behaves differently from its forced value, but some Scheme implementions are such that there is no means by which a promise can be operationally distingished from its forced value. Kawa is a hybrid: Kawa tries to minimize the difference between a forced promise and its forced value, and may freely optimize and replace a forced promise with its value.

Blank promises

A blank promise is a promise that doesn't (yet) have a value or a rule for calculating the value. Forcing a blank promise will wait forever, until some other thread makes the promise non-blank.

Blank promises are useful as a synchronization mechanism - you can use it to safely pass data from one thread (the producer) to another thread (the consumer). Note that you can only pass one value for a given promise: To pass multiple values, you need multiple promises.

(define p (promise))
(future ;; Consumer thread
  (begin
    (do-stuff)
    (define v (force promise)) ; waits until promise-set-value!
    (do-stuff-with v)))
;; Producer thread
... do stuff ...
(promise-set-value! p (calculate-value))

promise

Calling promise as a zero-argument constructor creates a new blank promise.

This calls the constructor for gnu.mapping.Promise. You can also create a non-blank promise, by setting one of the value, alias, thunk, or exception properties. Doing so is equivalent to calling promise-set-value!, promise-set-alias!, promise-set-thunk!, or promise-set-exception! on the resulting promise. For example: (delay exp) is equivalent to:

(promise thunk: (lambda() exp))

The following four procedures require that their first arguments be blank promises. When the procedure returns, the promise is no longer blank, and cannot be changed. This is because a promise is conceptually placeholder for a single “not-yet-known” value; it is not a location that can be assigned multiple times. The former enables clean and safe (“declarative") use of multiple threads; the latter is much trickier.

promise-set-value! promise value

Sets the value of the promise to value, which makes the promise forced.

promise-set-exception! promise exception

Associate exception with the promise. When the promise is forced the exception gets thrown.

promise-set-alias! promise other

Bind the promise to be an alias of other. Forcing promise will cause other to be forced.

promise-set-thunk! promise thunk

Associate thunk (a zero-argument procedure) with the promise. The first time the promise is forced will causes the thunk to be called, with the result (a value or an exception) saved for future calls.

Lazy and eager types

promise[T]

This parameterized type is the type of promises that evaluate to an value of type T. It is equivalent to the Java interface gnu.mapping.Lazy<T>. The implementation class for promises is usually gnu.mapping.Promise, though there are other classes that implement Lazy, most notably gnu.mapping.Future, used for futures, which are promises evaluated in a separate thread.

Note the distinction between the types integer (the type of actual (eager) integer values), and promise[integer] (the type of (lazy) promises that evaluate to integer). The two are compatible: if a promise[integer] value is provided in a context requiring an integer then it is automatically evaluated (forced). If an integer value is provided in context requiring a promise[integer], that conversion is basically a no-op (though the compiler may wrap the integer in a pre-forced promise).

If a fully-lazy language there would be no distinction, or at least the promise type would be the default. However, Kawa is mostly-eager language, so the eager type is the default. This makes efficient code-generation easier: If an expression has an eager type, then the compiler can generate code that works on its values directly, without having to check for laziness.

Threads

There is a very preliminary interface to create parallel threads. The interface is similar to the standard delay/force, where a thread is basically the same as a promise, except that evaluation may be in parallel.

future expression

Creates a new thread that evaluates expression.

(The result extends java.lang.Thread and implements gnu.mapping.Lazy.)

force thread

The standard force function has generalized to also work on threads. If waits for the thread's expression to finish executing, and returns the result.

runnable function

Creates a new Runnable instance from a function. Useful for passing to Java code that expects a Runnable. You can get the result (a value or a thrown exception) using the getResult method.

synchronized object form ...

Synchronize on the given object. (This means getting an exclusive lock on the object, by acquiring its monitor.) Then execute the forms while holding the lock. When the forms finish (normally or abnormally by throwing an exception), the lock is released. Returns the result of the last form. Equivalent to the Java synchronized statement, except that it may return a result.

Exception handling

An exception is an object used to signal an error or other exceptional situation. The program or run-time system can throw the exception when an error is discovered. An exception handler is a program construct that registers an action to handle exceptions when the handler is active.

If an exception is thrown and not handled then the read-eval-print-loop will print a stack trace, and bring you back to the top level prompt. When not running interactively, an unhandled exception will normally cause Kawa to be exited.

In the Scheme exception model (as of R6RS and R7RS) exception handlers are one-argument procedures that determine the action the program takes when an exceptional situation is signaled. The system implicitly maintains a current exception handler in the dynamic environment. The program raises an exception by invoking the current exception handler, passing it an object encapsulating information about the exception. Any procedure accepting one argument can serve as an exception handler and any object can be used to represent an exception.

The Scheme exception model is implemented on top of the Java VM's native exception model where the only objects that can be thrown are instances of java.lang.Throwable. Kawa also provides direct access to this native model, as well as older Scheme exception models.

with-exception-handler handler thunk

It is an error if handler does not accept one argument. It is also an error if thunk does not accept zero arguments. The with-exception-handler procedure returns the results of invoking thunk. The handler is installed as the current exception handler in the dynamic environment used for the invocation of thunk.

(call-with-current-continuation
  (lambda (k)
   (with-exception-handler
    (lambda (x)
     (display "condition: ")
     (write x)
     (newline)
     (k 'exception))
    (lambda ()
     (+ 1 (raise ’an-error))))))
       ⇒ exception
       and prints condition: an-error
(with-exception-handler
 (lambda (x)
  (display "something went wrong\n"))
 (lambda ()
  (+ 1 (raise ’an-error))))
    prints something went wrong

After printing, the second example then raises another exception.

Performance note: The thunk is inlined if it is a lambda expression. However, the handler cannot be inlined even if it is a lambda expression, because it could be called by raise-continuable. Using the guard form is usually more efficient.

raise obj

Raises an exception by invoking the current exception handler on obj. The handler is called with the same dynamic environment as that of the call to raise, except that the current exception handler is the one that was in place when the handler being called was installed. If the handler returns, then obj is re-raised in the same dynamic environment as the handler.

If obj is an instance of java.lang.Throwable, then raise has the same effect as primitive-throw.

raise-continuable obj

Raises an exception by invoking the current exception handler on obj. The handler is called with the same dynamic environment as the call to raise-continuable, except that: (1) the current exception handler is the one that was in place when the handler being called was installed, and (2) if the handler being called returns, then it will again become the current exception handler. If the handler returns, the values it returns become the values returned by the call to raise-continuable.

(with-exception-handler
  (lambda (con)
    (cond
      ((string? con)
       (display con))
      (else
       (display "a warning has been issued")))
    42)
  (lambda ()
    (+ (raise-continuable "should be a number")
       23)))
      prints: should be a number
      ⇒ 65

guard variable cond-clause^+ body

The body is evaluated with an exception handler that binds the raised object to variable and, within the scope of that binding, evaluates the clauses as if they were the clauses of a cond expression. That implicit cond expression is evaluated with the continuation and dynamic environment of the guard expression. If every cond-clause’s test evaluates to #f and there is no else clause, then raise-continuable is invoked on the raised object within the dynamic environment of the original call to raise or raise-continuable, except that the current exception handler is that of the guard expression.

(guard (condition
         ((assq 'a condition) => cdr)
         ((assq 'b condition)))
  (raise (list (cons 'a 42))))
      ⇒ 42
(guard (condition
         ((assq 'a condition) => cdr)
         ((assq 'b condition)))
  (raise (list (cons 'b 23))))
      ⇒ (b . 23)

Performance note: Using guard is moderately efficient: there is some overhead compared to using native exception handling, but both the body and the handlers in the cond-clause are inlined.

dynamic-wind in-guard thunk out-guard

All three arguments must be 0-argument procedures. First calls in-guard, then thunk, then out-guard. The result of the expression is that of thunk. If thunk is exited abnormally (by throwing an exception or invoking a continuation), out-guard is called.

If the continuation of the dynamic-wind is re-entered (which is not yet possible in Kawa), the in-guard is called again.

This function was added in R5RS.

read-error? obj

Returns #t if obj is an object raised by the read procedure. (That is if obj is a gnu.text.SyntaxException.)

file-error? obj

Returns #t if obj is an object raised by inability to open an input or output port on a file. (This includes java.io.FileNotFoundException as well as certain other exceptions.)

Simple error objects

error message obj ...

Raises an exception as if by calling raise on a newly allocated simple error object, which encapsulates the information provided by message (which should a string), as well as any obj arguments, known as the irritants.

The string representation of a simple error object is as if calling (format "#<ERROR ~a~{ ~w~}>" message irritants). (That is the message is formatted as if with display while each irritant obj is formatted as if with write.)

This procedure is part of SRFI-23, and R7RS. It differs from (and is incompatible with) R6RS's error procedure.

error-object? obj

Returns #t if obj is a simple error object. Specifically, that obj is an instance of kawa.lang.NamedException. Otherwise, it returns #f.

error-object-message error-object

Returns the message encapsulated by error-object, which must be a simple error object.

error-object-irritants error-object

Returns a list of the irritants (other arguments) encapsulated by error-object, which must be a simple error object.

Named exceptions

These functions associate a symbol with exceptions and handlers: A handler catches an exception if the symbol matches.

catch key thunk handler

Invoke thunk in the dynamic context of handler for exceptions matching key. If thunk throws to the symbol key, then handler is invoked this way:

(handler key args ...)

key may be a symbol. The thunk takes no arguments. If thunk returns normally, that is the return value of catch.

Handler is invoked outside the scope of its own catch. If handler again throws to the same key, a new handler from further up the call chain is invoked.

If the key is #t, then a throw to any symbol will match this call to catch.

throw key arg ...

Invoke the catch form matching key, passing the args to the current handler.

If the key is a symbol it will match catches of the same symbol or of #t.

If there is no handler at all, an error is signaled.

Native exception handling

primitive-throw exception

Throws the exception, which must be an instance of a sub-class of java.lang.Throwable.

try-finally body handler

Evaluate body, and return its result. However, before it returns, evaluate handler. Even if body returns abnormally (by throwing an exception), handler is evaluated.

(This is implemented just like Java's try-finally. However, the current implementation does not duplicate the handler.)

try-catch body handler ...

Evaluate body, in the context of the given handler specifications. Each handler has the form:

var type exp ...

If an exception is thrown in body, the first handler is selected such that the thrown exception is an instance of the handler's type. If no handler is selected, the exception is propagated through the dynamic execution context until a matching handler is found. (If no matching handler is found, then an error message is printed, and the computation terminated.)

Once a handler is selected, the var is bound to the thrown exception, and the exp in the handler are executed. The result of the try-catch is the result of body if no exception is thrown, or the value of the last exp in the selected handler if an exception is thrown.

(This is implemented just like Java's try-catch.)