Eric and I were discussing scoping in Scheme and Python earlier today, our third over the past few weeks – and we finally nailed it shut. The first time he brought up dynamic scoping in Common Lisp and how Prof. Friedman dislikes it; the second was on how Python appears to have dynamic scoping (which turns out to be true, pre-Python 2.2), and now, thanks to Wikipedia, I think we have it right.
Provided Eric gets the H211 Introduction to Programming (Honors) class, which is in Python, and I get the C211 Introduction to Programming (Scheme), our discussion should stand us in good steed, though funnily today I played the Python guy and he played the Scheme one.
I’m going to show the examples, both in Scheme and Python; the first one in each section would appear to show that the language in question features dynamic scoping, which is incorrect as both actually do lexical scoping.
(let ((pi 3.1415)) (define area (lambda (r) (* pi r r))) (display (area 10)) (newline) (set! pi 3) (display (area 10)))
(let ((pi 3.1415)) (define area (lambda (r) (* pi r r))) (display (area 10)) (newline) (let ((pi 3)) (display (area 10))))
pi = 3.1415 def area(r): return pi*r*r area(10) pi = 3 area(10)
pi = 3.1415 pi_holder = 10 def create_area(): pi_holder = pi # local pi_holder, different from pi_holder outside def area(r): return pi_holder*r*r return area area = create_area() area(10) # 314.15 pi_holder # Still 10 pi_holder = 3 area(10) # Still 314.15
The above works, but is a bit problematic. I introduced pi_holder=10 to show that, (1), pi_holder inside of create_area() is a local variable; (2), that this local pi_holder is the one that is in area‘s scope, and thus changing the value of pi_holder does not affect it.
Isn’t it easier to just do pi = pi ? Well, that does not work. My initial hunch was that Python reads the LHS of the expression, decided pi has been redeclared as a local variable, and thus since it’s not been initialized it got confused trying to assign it the value of itself. But it’s actually worse; this code does not work either:
x = 42 def local_var_test(): temp = x print temp # 42 x = temp print x # 42?
Surprise! Python won’t let you do that either. Take out the last two lines and the code works, though. Basically, if in the block a variable is declared anywhere, it is a local variable everywhere in that block, and trying to refer to a variable declared in the surrounding scope, even before the local declaration, will fail.
But this is where default parameters come in handy. A better way to rewrite the clunky code above is as follows:
pi = 3.1415 def create_area(pi = pi): def area(r): return pi*r*r return area area = create_area() area(10) #314.15 pi = 3 area(10) #314.15
So Python has static scoping after all. The thing to bear in mind is that Scheme functions are named closures, while Python functions inherit the surrounding scope, so to freeze the variables you depend on you have to wrap your function definition inside another function that copies in the values you need into its local variables.
And the funny thing is, I started the day trying to find good dynamic languages that run on the Java platform (platform envy, I guess, since .NET more prominently touts its language neutrality). Sun’s finally catching up, though – Tim Bray wrote a few months back about the Coyote project to support dynamic languages in Sun’s open source IDE, NetBeans, and pointed to an interesting Sun-developed scripting language, Pnuts. Which reminded me of Groovy and Boo.
Googling for groovy boo .net – Groovy being a Ruby-like scripting language for Java that received a lot of attention a few months ago, and then taken some flak over its development model, and Boo being the Python-like language for .NET – yields this very interesting Slashdot discussion that led me to such intriguing functional OO languages as Scala and Nice. .NET fans do not get to have all the fun!
Update – 2005/06/06
Realized a few days ago, but haven’t gotten round to posting about it, that I was unfairly comparing Scheme and Python, and that Python methods are closures in themselves. Note:
pi = 3.1415 def area(r): return pi*r*r print area(10) # 314.15 def test(): pi = 3 print area(10) # 314.15 test()
In the earlier example, overriding the value of pi with pi = 3 is the equivalent of doing (set! pi 3) in Scheme, i.e. it will change the value of the variable that both the top-level pi, which is the one that area knows. In a dynamic scope, which uses a stack to figure out which value assignment should apply, pi = 3 would affect the call to area just after it.