WhizzML Reference Manual

2.10 Error handling

Error handling in whizzml is based on exceptions raised either by the runtime or by the user via the raise procedure, which throws a given value as an error.

Objects thrown by raise can be of any type, but when the error comes from a predefined function, it will always be a map with, at least, the keys “message” and “code”.

There are two mechanisms For capturing exceptions: a basic, lower-level one based on the form handle, and the familiar try/catch construct (which is built as syntactic sugar over the basic handle functionality).

2.10.1 Signaling errors with raise

To signal an error in user code, use the raise keyword.

(raise <error>)

The above expression throws the value <error> as an exception, to be captured by an exception handler (see below), or, if none is active, to cause the program to stop.

As mentioned, you can throw any valid value as an error:

(raise "Connection problems")
(raise -23)
(raise {"message" "Empty dataset" "code" 42})

However, it is common to use a map to signal errors, as do by default all built-in procedures (if needed). The latter always contain the keys “message”, “code” and “instruction”; here’s an example of an error raised by the / operator:

{"message" "Error computing primitive operation '/': Divide by zero"
 "code" -1
 "instruction" {"source" {"lines" [1 1] "columns" [0 6]} "instruction" "apply"}}

But that is just a convention: raise will accept any WhizzML value and propagate it as an error to the currently active error handler.

2.10.2 Capturing errors with handle

Here is an example that captures a divide by zero exception using the handle syntactic form:

(handle (lambda (e) (log-error e) 42)
  (/ 1 0))  ;; => 42

The first argument of handle is a function of one argument that is called when the body of the handle form (the s-expressions following the error handling function) signals an error. The above program will log the captured error e (a divide by zero exception) and return 24: the value of the whole handle expression is the value returned by the handle function (when an error is thrown) or the value of its body if no error is raised.

In general,

(handle <handler> <body>)

registers the single-argument procedure <handler> as the active error handler while the forms in <body> are being executed. If the evaluation of <body> raises an error, the value thrown will be passed to the <handler> procedure and the value of the handle expression will be the value that the call to <handler> returns.

The handler can also be a variable whose value is a single-argument procedure, as in the following example:

(define (on-error e)
  (let (code (get e "code"))
    (cond (= e -1) 0
          (= e 1) -5
          (raise e))))

(handle on-error
  (when (negative? (get-x))
    (raise {"code" -1 "message" "Error: negative x"}))
  (do-something (get-x)))

As you can see, the body of handle can contain more than one form and it is legit to re-raise errors (or throw new ones) inside an error handler: they will be passed to the previously registered handler, if any, or just propagate to the top-level and stop the program otherwise.

2.10.3 Capturing errors with try/catch

WhizzML also includes syntactic sugar for handling errors via the try and catch keywords.

(try <body> (catch <id> <handler-body>))

executes <body> and, in case an error is raised, binds it to the variable <id> which is in the scope of <handler-body> , to which control is transferred. In other words, the try/catch form is equivalent to

(handle (lambda (<id>) <handler-body>) <body>)

For instance:

(try
  (log-info "Trying primary source")
  (create-dataset {"source" "source/123678907959482245aa31"})
  (catch e
    (log-warn "Could not create primary source: " e)
    (create-dataset {"source" "source/12345678901234567890abcd"})))

2.10.4 System errors

Built-in and standard library procedures always raise errors in the form of a map with keys “message” and “code”, as in the following example:

{"code" -10
 "message" "Error computing primitive operation '/': Divide by zero"}

The error codes used by built-in and standard whizzml procedures are shown in Table 2.1 .

Code

Cause

-10

Division by zero

-15

Key not found

-20

Empty list

-25

List of less than two elements

-30

Arguments out of range

-40

Incorrect number of arguments

-50

Error handling BigML resource

-60

BigML resource creation failed

-100

Generic exception

Table 2.1 Error codes

In addition to these WhizzML system codes, errors raised while creating, modifying or fetching API resources use the same codes as the API, as listed in the API documentation. For instance, here is an error thrown while trying to create a source with a malformed request; the code below:

(handle (lambda (e) e)
        (create-dataset {"source" "source/123456789012345678901234"}))

will evaluate to a map value (thrown by the standard library function create-datsaet), that contains, at least, two keys:

{"message"
 "Error computing primitive operation 'create': Id does not exist",
 "code" -1201}