Conditions & Restarts

Atomo uses a condition system very similar to Common Lisp's.

Signals

signal: v  @ok

Sends a value to all bound handlers, nearest-first.

Example:

> { signal: @foo; signal: @bar } bind: { @foo -> "fooing" print }
fooing
@ok
error: v  any

Signals v as an error. If the signal: call exits normally, a debugger is started. Hence, this method never exits normally.

Example:

> { "x" print; error: 1; "y" print } call
x
ERROR: <error 1>
warning: v  @ok

Similar to error:, but instead of starting a debugger, prints the error to *error-output*.

Example:

> { "x" print; warning: 2; "y" print } call
x
WARNING: 2
y
"y"

Handlers

action bind: handlers  any
  | action is-a?: Block
  | handlers is-a?: Block

Performs action, with a handler for all signals matched in handlers block. Returns the result of action.

The contents of handlers should be Association expressions, e.g. pattern -> responder, where pattern is a match on the signal value, and responder is either a Block (which will be called with the signal value itself as an argument) or an expression to be evaluated in response to the signal.

Example:

> { signal: @foo } bind: { @foo -> "fooed!" print }
fooed!
@ok
> { signal: @(bar: 42) } bind: { @(bar: x) -> @(fooed: x) print }
@(fooed: 42)
@ok
> { error: @uh-oh } bind: { Error -> { e | @(got-error: e) print } }
@(got-error: <error @uh-oh>)
ERROR: <error @uh-oh>

A responder may often invoke restart: or restart:with:, which is the only way they can affect the return value of action.

After a handler responds to a signal, the signal continues outward to the next recently bound handler:

Example:

> { { signal: @foo } bind: { @foo -> "me first!" print } } bind: { @foo -> "no me!" print }
me first!
no me!
@ok
action with-restarts: restarts  any
  | action is-a?: Block
  | restarts is-a?: Block

Call action, binding the restarts described by restarts. These restarts, when invoked, will replace the result of action.

The contents of restarts should be Association expressions, e.g. name -> expression, where name is the name for the restart, and expression is the action to be performed when the restart is invoked.

Example:

> { { x } with-restarts: { ignore -> @ok } } bind: { Error -> restart: 'ignore }
@ok
action with-restarts: restarts bind: handlers  any
  | action is-a?: Block
  | restarts is-a?: Block
  | handlers is-a?: Block

Trivial macro which just expands to with-restarts: and bind:.

restart: name  any
  | name is-a?: Expression

Finds restart name and jumps to it.

Example:

> action := { signal: @foo; "hi" print } with-restarts: { skip -> @skipped }
@ok
> { action } bind: { @foo -> restart: 'skip }
@skipped
restart: name with: (... args)  any
  | name is-a?: Expression

Invokes restart name with args passed to the block.

Example:

> action := { signal: @foo; "hi" print } with-restarts: { use-value -> { v | v } }
@ok
> { action } bind: { @foo -> restart: 'use-value with: 42 }
42
find-restart: name  in?: [@(ok: Restart), @none]
  | name is-a?: Expression

Looks for a restart named name.

Example:

> action := { signal: @foo; "hi" print } with-restarts: { skip -> @skipped }
@ok
> { action } bind: { @foo -> [find-restart: 'skip, find-restart: 'bar] print }
ERROR: <error @(did-not-understand: <message <object> text: "<restart">)>

Traditional Exceptions

These methods are specific to errors, and provide a system similar to that of traditional exception handling. They are defined in terms of the more general condition system.

action catch: recover  any
  | action is-a?: Block
  | recover responds-to?: @call:

Execute action call, catching any errors and passing them to recover for handling.

Yields the result of action or recover.

Example:

> { error: @error } catch: { e | @(got: e) }
@(got: <error @error>)
> { @ok } catch: { @caught }
@ok
> { error: "hi" } catch: @print
<error "hi">
<error "hi">
action catch: recover ensuring: cleanup  any
  | [action, recover, cleanup] all?: @(is-a?: Block)

Execute action call, catching any errors and passing them to recover for handling, and ensuring that cleanup is called after the action is executed.

Yields the result of action or recover.

Example:

> { @ok } catch: { @caught } ensuring: { "cleaning up" print }
cleaning up
@ok
> { error: @not-ok } catch: { @caught } ensuring: { "cleaning up" print }
cleaning up
@caught
action handle: branches  any
  | action is-a?: Block
  | branches is-a?: Block

Similar to catch:, but branches is a block containing exception patterns bound to the action to take if they're thrown. If none of the exceptions match any of the patterns, the exception is re-thrown.

Example:

> { error: @foo } handle: { @foo -> 1; @bar -> 2 }
1
> { error: @bar } handle: { @foo -> 1; @bar -> 2 }
2
> { error: @baz } handle: { @foo -> 1; @bar -> 2 }
ERROR: <error @baz>
> { @ok } handle: { @foo -> 1; @bar -> 2 }
@ok
action handle: branches ensuring: cleanup  any
  | [action, branches, cleanup] all?: @(is-a?: Block)

Similar to handle:, but with an ensuring: block.

Example:

> { error: @foo } handle: { @foo -> 1 } ensuring: { "hi" print }
hi
1
> { error: @baz } handle: { @foo -> 1 } ensuring: { "hi" print }
hi
ERROR: <error @baz>
> { @ok } handle: { @foo -> 1 } ensuring: { "hi" print }
hi
@ok
action ensuring: cleanup  any
  | action is-a?: Block
  | cleanup is-a?: Block

Execute action call, ensuring that cleanup is called after the action is executed.

Yields the result of action.

Example:

> { @ok } ensuring: { "cleaning up" print }
cleaning up
@ok
> { error: @not-ok } ensuring: { "cleaning up" print }
cleaning up
ERROR: <error @not-ok>
value ensuring: cleanup do: action  any
  | cleanup is-a?: Block
  | action is-a?: Block

Similar to ensuring:, but value is passed to both blocks. This makes things like file opening and closing very natural.

Yields the result of action.

Example:

> File (new: "example-file") ensuring: @close do: @(display: "Hello!")
"Hello!"