Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.



> You expose implementation details to the outside with those recover points.

Of course not, no more than an exception or an error code "exposes implementation details". Condition restarts are part of a library's API, since they're a way for library users to interact with the library (in this case, to customize the handling of some error conditions)

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

How and why is it a better behavior, especially if — unlike this contrived example — there are 5 frames of foreign code between the caller and the condition being signalled? Return or exception will have to unwind those frames, the point of a condition is that you don't, instead right there the library can provide a hook through which the library user can respond to the question: "how do I handle [this error]?"


That's one idea of the Lisp condition system: you don't return. The handler gets called on error and the full context is still there. The context includes all bindings, dynamic handlers and all possible restarts. The restarts itself are most often generic: Abort, Continue, Use Value, Store Value, ... There can also be several of those. For example the log error could be restarted by using a Abort error to abort a specific action (say, a compilation of a file) , to abort the whole process (say, a compilation of a whole system).


My favored method: return an error object, which includes all the necessary context. Of course, the library writer must anticipate what information might be necessary, but with condition handling he also has to anticipate, what restarts are necessary.


if you return, you can't continue.

With the condition system you implement restarts as a service to the user. You don't have to provide any one. In a typical Lisp system there will be a default handler.

The more restarts you implement, the more choice the user may gets. The handler can also decide which and how many restarts it wants to expose to the user, if at all.


> My favored method: return an error object

Right, so you've unwound your whole stack and now the original caller has to re-set the initial conditions (hoping all the state was discarded with the stack and none leaked), alter whatever needs to be changed (assuming and hoping there's even a way to get a handle on that) and re-wind the whole stack from scratch.

> Of course, the library writer must anticipate what information might be necessary, but with condition handling he also has to anticipate, what restarts are necessary.

Just as you have to "include all necessary context" in your solution...


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.


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?


> 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])


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.


If you want the restart, you need to take advantage of the PEP-342 two-way yield(), as in:

    ...
    except SomethingBad:
        yield None 
        restartRequest = yield None
        if restartRequest:
            do whatever is necessary to go on
The problem with the above is that the double yield() ugly. You cannot do this with single yield() because the caller would have to know in advance that next() will return None.


Maybe something like: https://gist.github.com/3598432

although perhaps passing a 'handlers' object down, with which handler functions could be registered might be better?


Generic sequences are coming to common lisp. I'm working on a simple implementation inspired by clojure which I'll be posting to /r/lisp in the coming week.


Do not separate errors and entries. Instead use something like Haskell's Maybe, but instead of an empty Nothing-object you return something which contains error information:

  def parseLogInteractively(file):
    entries = list()
    for line in file:
      entry = parseEntry(line)
      if entry.failed:
        entry = askToFixEntry(entry)
      entries.append(entry)
    return entries
A design question arises: Do you need to fix parse errors before you can proceed with next entry? If not, the error handling can be done by whomever calls parseLog.


The abstraction problem remains: you are reimplementing parseLog every time you write a new handler, as I described in section 1.

(By the way, you're looking for Haskell's Either type, but ([a],[b]) and [Either a b] are equivalent here.)


No, you only implement this one:

  def parseLog(file):
    list = []
    for line in file:
      list.append(parseEntry(line))
    return list
Or better (more pythonic imho):

  def parseLog(file):
    for line in file:
      yield parseEntry(line)
The second version is lazy (if file reading is lazy), so you can even abort parsing or fix the state on an error. Here are your different versions all reusing parseLog from above and equivalent line count to condition handling:

  def parseLogLoudly(file):
    for entry in parseLog(file):
      if entry.failed:
        print "Warning:", entry.message
      else
        yield entry

  def parseLogSilently(file):
    for entry in parseLog(file):
      if not entry.failed:
        yield entry

  def parseLogInteractively(file):
    for entry in parseLog(file):
      if entry.failed:
        yield askToFixEntry(entry)
      else
        yield entry
(Haskell's Either has "Left a" or "Right b". I want something like "Just value" or "Error msg", but you are right Either is nearer than Maybe)


Haskell's Either type is conventionally used exactly like that--pretend that Right val is Just val and Left err is Error err.

The reason it doesn't have those names is because it is more general; you can also use Either to model things like early termination. The generic names just make it clearer that it isn't exclusively for error handling unlike exceptions. In other languages using exceptions for more generic control flow is considered poor style, but in Haskell using Either for non-error cases is completely reasonable.


(It's conventional to use Left for errors and Right for successes.)


Sounds like this is just an opaque way of providing a strategy or callback to the parseLog method. Why not something along the lines of (scala):

  def parseLogInteractively(file) = parseLines(file.readLines(), error => askToFixEntry(e.txt))

  def parseLines(lines, errorHandler) = lines flatMap {
    tryParseLine(_) fold {success(l) => Some(l)} {error => errorHandler(error)}
  }


> Why not something along the lines of (scala):

Because that only works for very flat stacks, otherwise your callback will start infecting more and more method less and less related to what's actually causing the error, and then you'll have to move to a mapping of callbacks as different parts of the stack will want their own error callback.


Do you really want to handle error conditions in a resumey way far further up the stack? I'd expect most cases are either handle the error close to the cause, or a single application-wide handler (which could be provided with dependency injection).

The condition handling approach just seems too magic to me. It seems to mean the "inner" code calls another method defined somewhere else - but that "somewhere else" is defined by the call stack, something that's not really very visible in the code.


> Do you really want to handle error conditions in a resumey way far further up the stack?

"Way far" is relative and depends on a number of factor. And I don't consider half a dozen stack frames "way far", it's very easy to attain when using a coding style emphasizing small short methods which call one another.

You also have no bloody idea how the library works internally.

> I'd expect most cases are either handle the error close to the cause

Again, "close" and "far" is relative. I want to handle the error at the closest point to its triggering, but I'm also limited by the data available to know how to handle it. One pushes the handler down the stack, the other up.

> The condition handling approach just seems too magic to me.

There's nothing magic to it.

> It seems to mean the "inner" code calls another method defined somewhere else - but that "somewhere else" is defined by the call stack

Welcome to dynamic scoping, there are cases where it's useful.

> something that's not really very visible in the code.

How is "I'm signaling a condition" not very visible in the code? It says right there that it signals a condition.


"The condition handling approach just seems too magic to me."

They're nothing compared to Lisp's macro facility.


I'm not exactly a fan of that either


Letting the caller do the handling and giving it access to the dynamic environment where the condition was signaled is a feature.

This paper by Pitman helps to understand the design choices www.nhplace.com/kent/Papers/Condition-Handling-2001.html


Controversial programming opinion of the day: not knowing implementation details will come back to bite you, overexposure is better than underexposure.

Anyway, I think the basic idea is just a small extension of being able to pause the program counter, change stuff, do something else, and at one's option continue where one left off, all without requiring a debugger. Nothing new if you've been exposed to the Lisp world. Typical examples of utility being recovering from longComputation() then failing without repeating work and hot-fixing a long-running server process. I agree this example isn't a very good one, I like your design you commented on for this problem better, I think in this case even "an error in parsing a line is not exceptional" would suffice in defeating the example. (Though in Python even syntax errors are implemented with exceptions, so...)


How would you accomplish somthing similar without exposing implementation details? It seems to me "this function may throw these conditions" is more a documentation issue. I think every other system I've seen, (esp C) exposes more details.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: