Skip to content

Instantly share code, notes, and snippets.

@phoe
Last active October 13, 2020 09:40
Show Gist options
  • Save phoe/35e596c3746d8d0ea82aaf1bb51781fc to your computer and use it in GitHub Desktop.
Save phoe/35e596c3746d8d0ea82aaf1bb51781fc to your computer and use it in GitHub Desktop.

@aheejin The Common Lisp condition system has only one operator for executing cleanup forms, named unwind-protect - it is equivalent to Java's finally.

However, the Common Lisp condition system works on a completely different basis than e.g. Java exception handling, with the most important difference being: when a Common Lisp condition (this includes errors) is signaled, the stack is wound further instead of being unwound. Only then a transfer of control may (or, in case of error conditions, must) happen.

Under the hood, the Common Lisp condition system is implemented in Common Lisp itself (see example here) on top of its primitive control-flow operators - tagbody/go, block/return-from, and catch/throw.

  • go is the "goto" operator that jumps to a label defined in a tagbody;
  • return-from returns a value from a matching block in lexical scope;
  • throw returns a value from a matching catch in dynamic scope.

For more detailed information about the CL standard, we could consult CLHS 5.2 Transfer of Control to an Exit Point. Let's copy some text from there:

When a transfer of control is initiated by go, return-from, or throw the following events occur in order to accomplish the transfer of control. Note that for go, the exit point is the form within the tagbody that is being executed at the time the go is performed; for return-from, the exit point is the corresponding block form; and for throw, the exit point is the corresponding catch form.

  1. Intervening exit points are "abandoned" (i.e., their extent ends and it is no longer valid to attempt to transfer control through them).
  2. The cleanup clauses of any intervening unwind-protect clauses are evaluated.
  3. Intervening dynamic bindings of special variables, catch tags, condition handlers, and restarts are undone.
  4. The extent of the exit point being invoked ends, and control is passed to the target.
  • As long as we have dynamic/fluid variables (which, in the worst case, we can implement ourselves on top of lexical variables and a primitive unwind operator), we can resolve point 3 on top of WASM.
  • We do not need to care about point 1, since it is undefined behavior in Common Lisp, and so the burden is on the programmer.
  • This leaves point 2, which is required to perform nested cleanup forms (think nested unwind-protect, which is very common in practice) and point 4, which is the primitive jump operator that we've already used in point 3.

If you need any further Lisp-related help, I'll be available to answer. (Shameless plug: I've described this mechanism in detail in an upcoming book, The Common Lisp Condition System, that will be released soon; I hope that my knowledge in the matter is of help in this issue.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment