Heck, it's easier to handle recovering from an error by just restarting a process and letting it follow the normal unit logic.
Do have any knowledge of how CL and Erlang/Elixir/OTP are similar or vary on error handling?
Heck, it's easier to handle recovering from an error by just restarting a process and letting it follow the normal unit logic.
Do have any knowledge of how CL and Erlang/Elixir/OTP are similar or vary on error handling?
When an error occurs in a Common Lisp program the runtime signals a __condition__. A condition is an instance of a class that represents unexpected or otherwise exceptional situations that can arise during execution. When a condition is signaled, control searches for a registered handler that matches the type of the signaled condition. If one is found then control passes to the matching handler. If none is found then control passes to the default handler, which by default starts an interactive repl called a breakloop__.
The breakloop has access to the full dynamic state of the suspended computation, including all local and dynamic variables and all pending functions on the stack. From the breakloop a programmer can alter variable values and redefine functions and classes. For example, if you conclude that the error happened because of an incorrect function definition, you can redefine the function and tell the breakloop to resume execution as if the new definition had been called instead of the original one.
A __restart__ in this context is an option for continuing execution. Common Lisp's condition system offers the programmer the ability to choose from among available restarts in a breakloop, or to write a handler that will make the choice automatically when a condition is signaled, and it also offers the ability to define custom restarts.
(defun fn1 ()
(if (yes-or-no-p) (print :yes) (print :no)))
(defun fn2 ()
(fn1))
[yes-or-no-p](http://clhs.lisp.se/Body/f_y_or_n.htm) is an interactive function that reads from the stdin. However, you cannot programatically answer "yes" to fn1, as other functions in the call stack (fn2) has no way to know that fn1 halts because it waits for the input from the stdin. Instead, a condition system allows this: (defun fn1 ()
(restart-case (error "yes or no?")
(yes () (print :yes))
(no () (print :no))))
(defun fn2-interactive ()
(fn1))
(defun fn2-automated ()
(hander-bind ((error (lambda (c) (invoke-restart 'yes)))) ;; handler
(fn1)))
When you call fn2-automated, an error is signaled, handled by the handler, which invokes a restart 'yes, then :yes is printed. Interactivity is still maintained by fn2-interactive. yes or no?
[Condition of type SIMPLE-ERROR]
Restarts:
0: [YES] YES
1: [NO] NO
2: [RETRY] Retry SLIME REPL evaluation request.
3: [*ABORT] Return to SLIME's top level.
4: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1003167FA3}>)
See? All debugger menus are actually implemented as the restarts, set up at various call stacks. In this implementation of the debugger the way to select YES is to enter 0 or click it on the emacs buffer. In another debugger, it could be entering ":r 0" to the stdin. You can also implement your debugger function, which `read-line` the input stream and only recognizes a specific string, y or n, and set your function to `debugger-hook` to use it (I forgot to mention this in the above example).Having said that, practically, you wouldn't release a tool which has users interact at the debugger, at least for any level of more serious commercial work no?
As said, you can have a custom "debugger" which does not look like a debugger, but a general purpose user interface in the text terminal. For example, being "custom", it can also hide the restarts which are only meaningful for true debugging (like restart 2,3,4). (see detail [1])
It is possible for a debugger to even invoke a GUI (imagine a debugger hook which pops up a window with an OK button) or a web UI (sends a Websocket message to the client, wait for the user to click something on the browser, receive a reply and invoke the restart). In fact, SLIME is written this way: it overloads the debugger hook, and it sends and receives messages to the Emacs TCP client (thus you can click on the menu on an Emacs buffer to restart -- this action is notified back to the underlying lisp process via TCP, which then calls invoke-restart).
[1] You can obtain the list of restarts with COMPUTE-RESTARTS and filter unassociated restarts (similar to a method dispatch but is temporary / has dynamic extent -- see http://clhs.lisp.se/Body/09_adbd.htm).