Friday, December 3, 2010

Closures in imperative settings: hidden and not so hidden problems (Python)

Nowadays, the number of essentially imperative languages that have some degree of support for closures is not a small one. Even languages which traditionally do not have closures like C have been extended in order to support something which roughly behaves like a closure. E.g., the Apple blocks extension goes in this direction. Perhaps one day I will discuss this feature. Even the good old gcc nested function extension (with all its drawbacks) does the job. And in Java you can do with an anonymous class. Of course Python and Ruby have "almost" full support for closure. What do we mean with "almost full"? The point is that a closure is essentially a functional concept. In functional languages you usually don't change the contents of a variable. Either you can't (Haskell) or you shouldn't (Lisp). On the other hand imperative languages do not have the restriction and imperative programmers, even those using closures, do not think that it is a bad thing.  However, both Python and Ruby suffer greatly on this. In our first example we are using closures, although many imperative programmers may have written code like that without being aware they were dealing with closures. Consider a simple graphical program using the omnipresent Tkinter framework.
import Tkinter as tk

class Window(tk.Frame):
    def __init__(self, root):
        tk.Frame.__init__(self, root)
        self.value = 0
        def inc():
            self.value += 1
            self.label.config(text=str(self.value))
        self.label = tk.Label(text=str(self.value))
        self.label.pack()
        self.button = tk.Button(text="Increment", command=inc)
        self.button.pack()

root = tk.Tk()
w = Window(root)
w.pack()
tk.mainloop()
It may be not clear, but inc is a closure. In fact the self variable is a local variable to the __init__ method and is referenced inside inc, defined in __init__. The above code runs fine. However, if we do not define value as an attribute of self, errors arise. Consider this code:
import Tkinter as tk

class Window(tk.Frame):
    def __init__(self, root):
        tk.Frame.__init__(self, root)
        value = 0
        def inc():
            value += 1
            self.label.config(text=str(value))
        self.label = tk.Label(text=str(value))
        self.label.pack()
        self.button = tk.Button(text="Increment", command=inc)
        self.button.pack()

root = tk.Tk()
w = Window(root)
w.pack()
tk.mainloop()
It looks right, but it is not. In fact, a "UnboundLocalError: local variable 'value' referenced before assignment" exeception is thrown. Why? When executing value += 1 does not find any local variable value, because value is not local: it's local to the enclosing scope. And is not global. So the error. If you are wondering what happens if value is global (and in inc you ask for a global value variable)... things work. But every time a global is used a programmer dies somewhere. Who knows who's next... In fact in Python closure can read variables from the enclosing scope. They can modify them (for example using methods which mutates them, or like in the first example, rebinding an attribute). However, you can't "rebind" the variable. And since integers are immutable, += 1 "assigns" a new integer.

, , , ,

Powered by ScribeFire.

No comments: