
An Introduction to Condition Handling for Non-Lispers - lubutu
http://lubutu.com/soso/condition-handling-for-non-lispers
======
jahewson
> why don’t more languages have them?

My guess would be that they are not needed. Exceptions for anything other than
program death are something of an anti-pattern, potentially spreading the
internal details of some piece of code all the way up the stack.

My choice of implementation would be to pass a discriminator function which
takes an error and returns true if the error is recoverable. Then you have the
choice of either returning all the errors at the end or reporting each earlier
when the discriminator is called.

Edit: That's not to say that exceptions can't be used effectively, they just
mostly aren't.

~~~
mschaef
> My choice of implementation would be to pass a discriminator function which
> takes an error and returns true if the error is recoverable.

Conditions are close to exactly that + syntactic sugar.

`handler-bind` lets you establish a dynamic scope, inside of which specific
functions are called when specific conditions are signaled. Signaling a
condition = call the first handler function in the list that handles the
signaled condition. The handler is called _before the stack is unwound_ ,
which allows it to elect to either `throw`, invoke a restart, or ignore the
condition entirely.

This is in contrast to exceptions in (say) Java. `throw new FooException()`
does a lot of potentially destructive work before control gets transfered to
the exception handler: It allocates an exception object, which fully captures
the current thread's stack. (It's not too common, but this can be bypassed, if
you don't throw a _new_ exception object...) It unwinds the stack to the frame
that has a matching `catch`.

Only after those two things have happened does the exception handler get a
chance to decide how to proceed, and at that point, it's too late for many
recovery strategies. Conditions help this by making it easier to intercept the
process earlier.

Where they hurt is that they add another level of complexity to API design.
With Java style exceptions, if the function fails, it fails completely.
There's a convenient simplicity to that. With Lisp style conditions and
restarts, A function will let you know how things are going as it does its
work and then give you the opportunity to change its behavior midstream. It's
a lot more work to design that kind of API, and it's probably well past the
point of diminishing returns for most kinds of development.

~~~
jahewson
Great answer. I'm left wondering if the solution is more complex than the
problem.

~~~
lispm
There might be two different perspectives on this:

* automated error handling, mostly invisible to the user. In this case the condition system may give an attractive architecture, but it is not widely explored and it is not clear how it would integrate into a language like Java. There could be a benefit, but it is not explored by software architects. Apple's Dylan probably had something to it, but it was not widely used.

* error handling in interactive systems. That's where the Lisp community used/uses it mostly. The largest effort to use the condition system were in the OS of the MIT Lisp machine descendants - especially the one from Symbolics. It was used both in the development environment - which is tightly integrated in the OS. Most of the development tools were using it. But things like the networking code or the file system were also using it. The user interface is generally slightly more complex than current graphical user interfaces and it was expected that the user used this kind of power. When you get a dialog of restarts presented, the user needs more thinking about how to proceed than with a simple cancel/abort dialog box. There were also applications written using it and for that slightly simpler interfaces were used. For example the restarts were presented in dialogs and not in a debugger context.

It takes a bit of re-orientation to imagine a user interface, where the user
has the option to repair/retry a failed operation, instead of the usual
abort/redo. These benefits are easier to get in an integrated system (-> Lisp,
Smalltalk) - a model where several non-integrated tools interact in a
condition system is more difficult to imagine.

Today, most of the better Common Lisp systems are using the condition system
in their environment. There the power can be used, but at the same time it is
also easy to ignore and just use a default mode of using the abort restart -
without getting the benefits of thinking a bit more and investing the time to
use a more complex restart.

------
brudgers
Peter Seibel's explanation in the online version of _Practical Common Lisp_
Chapter 19:

[http://gigamonkeys.com/book/beyond-exception-handling-
condit...](http://gigamonkeys.com/book/beyond-exception-handling-conditions-
and-restarts.html)

 _Practical Common Lisp_ Table of Contents: <http://gigamonkeys.com/book/>

------
howeyc
For anyone that had trouble learning the condition system, or just wants
another example, this helped me:

<http://symbo1ics.com/blog/?p=1405>

~~~
qznc
That task here is: A 'compute-slopes' function gets a list of points and
returns a list of slops between consecutive points.

The problem: What if points have the same x-value, hence the slope is
undefined.

Solution 1 Exceptions: throw new DivisionByZero()

Solution 2 Condition Handling: provide the restarts point to (a) return nil,
(b) return 0, and (c) return a user-specified value.

Solution 3 Return Error: return Error("division by zero")

------
qznc
What I do not like about this: You expose implementation details to the
outside with those recover points. In the same sense exceptions in Java expose
implementation details, though.

For this specific example, the better behavior (imho) would be that parseEntry
always returns. So it returns ParseError instead of throwing it.

Basically, I want a more convincing example for condition handling.

~~~
lubutu
Suppose we were attempting to write parseLogInteractively by just returning
errors (as in idiomatic Go).

If parseLog just returned an error upon failure we would have to same problem
as exceptions do, as explained in section 1. So we have to make parseLog
return two lists: one of entries, and one of errors. We would then have to
search through the error list fixing and reparsing each error. In Python this
would be something like,

    
    
      def parseLogInteractively(file):
          return parseLinesInteractively(file.readLines())
    
      def parseLinesInteractively(lines):
          entries, errors = parseLines(lines)
          if errors:
              entries += parseLinesInteractively(askToFixEntry(e.text) for e in errors)
          return entries
    

Again we lose the ability to abstract the function 'parseLog'. And I don't
know about you, but this looks far worse to me than "resume FixEntry" in terms
of exposing implementation details.

~~~
oddthink
In python, I usually deal with this with generators. It's not as elegant as
conditions, but it works well enough. I've never tried to permit custom
restart-like behavior, but now that generators are coroutines, it should be
doable.

e.g.

    
    
        def parseEntry(line):
            try:
                parsed = foo(line)
                yield parsed
            except SomethingBad:
                yield None
    

Common lisp could really use some better coroutine support (and some way to
specify generic sequences). Sure, you can do it with macros, but it gets
horrible fast. Have you looked at SERIES, for example?

~~~
masklinn
> I've never tried to permit custom restart-like behavior, but now that
> generators are coroutines, it should be doable.

It would be extremely awkward, you'd need a big dispatch table handling the
generator's result and send()ing restarts up to the generator, and of course
you lose condition's ability to work _through_ stacks of blissfully unaware
code, and to have default behaviors (in Smalltalk, the default behavior — in
an interactive image — is to pop up a dialog asking the user if he wants to
unwind the stack [as in an exception], to open a debugger at condition point
or to try resuming the condition [under the assumption that the user fixed
some incorrect method, or checked his network access])

~~~
oddthink
Thanks for the pointer; I've been learning some Smalltalk lately, but I
haven't yet looked into the condition system.

And, yes, I'm not saying that manually implementing pseudo-restarts is
something I'd ever want to do, but some vague skeleton of the possibility is
there.

------
ataggart
If I understand correctly, the idea is that `parseLog` is a library function,
while `parseLogSilently` and `parseLogInteractively` are examples of callers
to that library.

If so, then the complexity of documentation and understanding of `parseLog` is
pretty high. The caller needs to not only know the different conditions (e.g.,
`ParseError`), but also the available recovery strategies for those conditions
(e.g., `SkipEntry`, `FixEntry`), and further that some of those strategies
require an additional caller-provided function (e.g., `askToFixEntry`) with
its own separate requirements (e.g., it takes an exception and returns a
line).

Why would it not be better, since there are a finite number of provided
recovery strategies, to simply hide that complexity within library-provided
functions? E.g.:

    
    
        (defn parse-log [file] ...) ; aborts on ParseError
        (defn parse-log-skip [file] ...) ; skips bad lines
        (defn parse-log-fix [file fix-fn] ...) ; emits (fix-fn line) for bad lines

~~~
lubutu
Notice 'parseLogLoudly'. You could not implement that using your set of
functions: you are not allowing for other code besides 'resume' inside the
'handle' blocks.

Also, I don't believe the mass duplication of code necessary for your approach
is acceptable, even within a library.

~~~
ataggart
>Notice 'parseLogLoudly'. You could not implement that

Fair point, and that's due to my failure in not providing an optional side-
effect function arg to `parse-log-skip`. And this brings up a related point,
namely that the author of the lib is still responsible for crafting the API.
For example, if the author of `parseLog` had not included `recover FixEntry`,
would it be possible for the caller to implement it via a condition system?
Would that even be a good idea? Using the pseudo-python, the only approach I
can think of would be:

    
    
        def parseLogInteractively(file):
          do:
            return parseLog(file)
          handle ParseError, e:
            line = askToFixEntry(e.text)
            retry
    

That reference to `line` is pretty horrific.

Perhaps this is one of those cases where a simplified example helps convey the
concept, but is too simple to show where other approaches would fall short.

>mass duplication of code

I'm not clear on what you mean. Multiple functions with different behaviour
seems not to altogether different from one function with implicit switches
that change its behaviour.

------
ajuc
Seems like useful feature to have.

Is it supposed to be only used for error handling (like exceptions) or can you
use it for unconventional control flow, too?

~~~
ken
Dan Weinreb (one of the Multics users who brought the Multics condition system
to Lisp) wrote about why "error" is not a great term:
[http://danweinreb.org/blog/what-conditions-exceptions-are-
re...](http://danweinreb.org/blog/what-conditions-exceptions-are-really-about)

Personally, I've not seen it used for arbitrary control flow. If you control
the syntax of things inside your block, then macros are probably good enough.
If you're passing values outside of your own block, then you probably need
continuations.

OTOH, Lisp programmers have a tendency to not care what something is "supposed
to be used for". Using Lisp at all is said to be "the most intelligent way to
misuse a computer". :-)

------
erikb
I think you are looking for the "with" statement! The feature you describe is
already there. And it works with objects, so it's reusable, extendable and
exchangeable if needed.

<http://effbot.org/zone/python-with-statement.htm>

~~~
masklinn
No, context management is only a small subset of what you can do with
condition (and does not involve restart), and also implies stack unwinding (as
do exceptions) amongst other things.

To TFAA: to talk about conditions but not use lisp syntax, you could have used
Smalltalk (the other language with conditions and restarts)

~~~
erikb
We are not talking "condition" from Lisp here, but the blog post that is
linked to this thread. And what the author is doing is asking for a "with"
statement without knowing that the feature is already there. "condition" might
be a powerful feature and I might even like it myself, but the post doesn't
show the power of "condition" to Pythoners, because the feature used in the
example already exists in the core language.

~~~
masklinn
> We are not talking "condition" from Lisp here

Of course we are.

> And what the author is doing is asking for a "with" statement

No, he most definitely is not, a with statement (or Common Lisp's version,
`unwind-protect`) does _not_ provide resuming, only restoring a previously-
stored context.

> the post doesn't show the power of "condition" to Pythoners, because the
> feature used in the example already exists in the core language.

You should try reading the post again rather than stop at the first paragraph,
because you're wrong.

