Thursday, January 27, 2011

Cleanup Strategies semantics for different languages

A couple of days ago, I was reading a nice Puzzle in Java Puzzles.

The book is full of interesting information regarding gotchas of the Java language and VM and also contains some useful insight for language developers (in the sense of "get right what Java designers got wrong").

The puzzle was about the return value of a function such as:

boolean f() {
  try {
    return true;
  } finally {
    return false;
  }
}


Of course, the function returns false according to Java specifics. The authors however argued that this kind of programs should be forbidden by language desigers as their semantics is rather counterintuitive. Personally, I'm not sure if a language has to become more complicated to avoid these kind of problems. I rather believe that this kind of stuff is under the responsability of the programmer.

Nonetheless, I decided to see what happened with different languages. Unsurprisingly, in Python it has the same semantics than in Java"

In [4]: def f():
...:     try:
...:         return True
...:     finally:
...:         return False
...:

In [5]: f()
Out[5]: False


Essentially, either you bane such programs (e.g., return is not allowed in a finally clause) or you can't avoid the problems: returning True would be far worse. Ruby does the very same thing:

def f()
  begin
    return true
  ensure
    return false
  end
end

irb(main):020:0% f
false


Essentially, the problem is with finally (well, ensure, in Ruby). On the other hand, if a language does not provide any cleanup mechanism error management is seriously more complicated. In fact, in Ruby it is not uncommon to use blocks for this purpose. In Python, we have the with statement. Considering that the with statement associates a block code with a context and cleanup happens in the __exit__ method of the context object, there is essentially no way that such unsurprising behaviour happens (the return value of the __exit__ block is not the return value of the function).

In [1]: class ContextExample(object):
...:     def __enter__(self):
...:         pass
...:     def __exit__(self, *stuff):
...:         return False
...:
...:
In [4]: def f():
...:     with ContextExample() as c:
...:         return True
...:
...:

In [5]: f()
Out[5]: True


However, before moving to other kind of error management strategies (RAII, with), we should notice that things in clojure are different:

(defn f[]
  (try
    true
    (finally
      (println "FOO")
       false)))

(println (f))


prints "FOO" and then true. Essentially the finally block is executed, but the value returned is not the one of the finally block. Surprising? Not really. In Clojure documentation is crystal-clear on this point:


(try expr* catch-clause* finally-clause?)
catch-clause -> (catch classname name expr*)
finally-clause -> (finally expr*)

The exprs are evaluated and, if no exceptions occur, the value of the last is returned. If an exception occurs and catch clauses are provided, each is examined in turn and the first for which the thrown exception is an instance of the named class is considered a matching catch clause. If there is a matching catch clause, its exprs are evaluated in a context in which name is bound to the thrown exception, and the value of the last is the return value of the function. If there is no matching catch clause, the exception propagates out of the function. Before returning, normally or abnormally, any finally exprs will be evaluated for their side effects.


Considering the Java heritage of Clojure, this may be surprising for Java programmaers (at least those crazy enough to use code resembling the one at the beginning of this page). However, such behaviour is exepected and welcome for Lisp programmers. Indeed the Lisp equivalent of "finally" is the lovely unwind-protect. And the doc of unwind-protect states that:


unwind-protect evaluates protected-form and guarantees that cleanup-forms are executed before unwind-protect exits, whether it terminates normally or is aborted by a control transfer of some kind. unwind-protect is intended to be used to make sure that certain side effects take place after the evaluation of protected-form.


Once again: the cleanup stuff can be used only for side effect. The return value is the one of the main expression.

2 comments:

Jeff said...

Thanks for pointing this out, it's good to know.

Unknown said...

I suppose you refer to the different semantics of clojure finally. In fact, I found it quite surprising, but it both makes sense and I do like it.