Referencing local variable before assignment results in unexpected behavior

I mean, I get the design philosophy of Python: take anything that might be confusing to someone and just disallow it. Reassigning an outer local form an inner function body? Nope, you have to use nonlocal. Scopes smaller than functions? No, if a local variable is visible somewhere in a function, it’s visible everywhere. That causes issues with closures capturing variables in loops, you say? Oh well. We didn’t really want people using closures anyway. It’s a point of view. But it makes for what feels to me like a very fussy, unsmooth programming experience. Moving the same code between a for loop and a comprehension? Might change what it does. Moving code in or out of an inner function? Also might change what it does. That’s just the way Python is: the meaning of code is very context dependent, you can’t just freely move things around and expect them to behave the same. This is true of imports as well: order matters very much in Python, whereas in Julia, if you change the order of your imports, as long as there are no warnings, it does the same thing both ways. The Python behavior is simpler but more brittle; the Julia behavior is more subtle but carefully designed so you can rearrange your code without problems.

Being able to smoothly move code around and have it mean the same thing is a pretty important principle in the design of Julia. One not-entirely-obvious reason that’s important is macros. Why? Well, when you write @foo x = f() you don’t know exactly how x = f() is going to be evaluated. Maybe @foo just adds a few expressions before and after x = f() like the @time macro does. Or maybe it makes x = f() the body of a closure and then calls that closure. You can’t tell. And it mostly doesn’t matter since x = f() behaves the same either way. If Julia behaved like Python, you’d have to know how @foo is implemented since you’d have to change it to be @foo nonlocal x = f() instead if it was implemented with a closure. Maybe there could be some macro tool to rewrite the expression to add nonlocal to any modified captures. But that’s hard to do correctly in all cases. (How does the macro know if something is global or nonlocal, for example?) And it’s another gotcha that macro writers need to watch out for and are likely to get wrong. The closure behavior in Julia (like Lisp, from which it was copied) means that you don’t have to worry about this. (Python’s solution: no closures or macros. Except it’s got closures now, they’re just awkward to use, and it’s edging towards macros and this is a gotcha that makes metaprogramming harder than necessary.)

11 Likes