Heheh. All this talk of thread titles had me go and review my solution to ex35 (Puny Python).
Although I got it working, the scoping is indeed wrong. I used a scoping rule from some other language. I should have used a call stack after all. I almost want to go back and write it over again, but it would be so much work!
Why do I need to go back? Because if you have an enclosed function, such as
x = 5
print("hello from inside! x==", x)
x = 3
When you call outside(), the inside() will print x==5 and then x==3 to reflect the change in the environment. From what I understand, this is not a closure. In my implementation of PunyPy/QuasiPy/whatever, the inside() will always print x==5, which is a closure.
Although first class functions can be passed to and called from any location, the environment in which the first class function was defined still persists on the stack. If there’s a change to a free variable in that particular defining environment, that change will be reflected every time inside() is called.
How did I get confused about this? Because I did a subsequent test where I had a recursive function which accumulated multiple definitions of the same variable. This looked like a bunch of closures to me because each definition of the inside function would return a free variable which was tied to each level of recursion. eg, inside might return “x==3” and inside might return “x==4”.
On the surface, this does indeed look like a closure, but it’s important to remember that a recursive function is very different from a loop. Each call to the recursive function, each recurse, has a distinct “frame” on the stack. When the call to the recursive function is complete, the free variables don’t just ‘go away’ when the recursive’s call frame is popped off the stack like I would have expected. Instead, they persist for as long as the inner-nested function definitions (aka 'inside(), which contain references to those free variables) exist. I don’t know what this means for how the call stack actually works under the hood in python, but this is what makes sense to me in practice.
This is why I was on the right track when I decided to implement a call stack. The structure I had imagined would have allowed for this persistence of free variables.
I feel silly about it. Kind of embarrassed, but hindsight… So would it be a big deal to go back to ex35 and try to fix this flaw? I’ll sleep on it.