4 ms·
The real trouble here is probably closures. function foo(x) { return function(y) { return x+y; }; } bar = foo(3) bar(5) // Returns 8, wait ho
by jules 16y ago
The real trouble here is probably closures.
function foo(x) { return function(y) { return x+y; }; }
bar = foo(3)
bar(5)
// Returns 8, wait how did the x in the inner function remember the 3?!
// It's supposed to be gone already because the call to foo is over...
One way to understand this is to notice that it always "just works". It seems like magic. The 'aha' for me was when I learned how closures are implemented.
- ww520 16y agoOne way to understand closure is to know that a function object actually contains both the instruction code and an environment table holding the variable bindings, bar = [ code = { return x+y; }, env = [x:3] ]. When bar is called, it uses x's value in its environment. That's closure. Easy, right?
- jules 16y agoYes, although it gets trickier with mutation. For example: x = 0 foo = function(){ x += 1 } bar = function(){ x -= 1 } Somehow you need to update bar's x too when you call foo and foo's x when you call bar. One way to do this by wrapping x in an object of its own, for example in a single element array that just contains x. I think the implementation of closures in Mono got this wrong, calling foo would have no effect on bar and vice versa (perhaps it's fixed now).
- ww520 16y agoI was hoping to keep thing nice and simple. :) You brought up a good point in the subtle problem in using closure. Yes, in some implementation of closure, both foo and bar access the same closed variable x. The best way to treat this is to view closed variables as global variables in different scopes. Global variables imply they are shared. e.g. x = 1 // environment in the global scope function baz() // environment in baz's scope shared by foo and bar x = 3 foo = function() { x += 1 } bar = function(){ x -= 1 } return [foo, bar] foo1, bar1 = baz() // create a baz_env1 [x:3] bar1() // baz_env1 [x:2] foo1() // baz_env1 [x:3] foo1() // baz_env1 [x:4] foo2, bar2 = baz() // create a baz_env2 [x:3] bar2() // baz_env2 [x:2] x = 10 // At this point, // global environment's x is 10 // baz_env1 x is 4 // baz_env2 x is 2 Hope that clarifies things.
- jacquesm 16y agoI've described a closure to myself as 'a temporary freeze on the scope and all variable bindings as they were when you got there the first time'. Is that correct?
- silentbicycle 16y agoYes - everything will act as if that stack frame was heap-allocated and still exists* . You can modify the value(s), define multiple functions sharing the same variable bindings, etc. A lot of common language features (such as conventional objects) can be implemented as syntactic sugar on top of closures. (Same with continuations.) * Compilers/interpreters are free to do optimizations that won't affect results, like only heap-allocating the closures that escape their scope.
- jacquesm 16y agoThanks!
- silentbicycle 16y agoWhen I'm struggling with what a programming concept means, I find it helpful to try implementing it in a toy interpreter. Efficiency is irrelevant, it just needs to model the concept correctly. Many concepts really aren't that complicated if you're not worried about optimization, and getting to a working model tends to shake out the aspects you don't completely understand. (This is also why books have exercises.)
- jacquesm 16y ago> When I'm struggling with what a programming concept means, I find it helpful to try implementing it in a toy interpreter. That's my 'I have understood' benchmark. If I can re-implement something without any reference to outside sources. My goal is to write a little lisp or scheme from the ground up from nothing but the lambda calculus and some bits & pieces. It'll be slow as molasses but it will help me to really grok it. That's why I've been reading up on all this stuff over the past year or so. After that I think I'm ready for something more involved, like clojure. I realize that this is not a very efficient way of learning but in the long run it tends to pay off. Knowing what goes on under the hood is an advantage.
- silentbicycle 16y agoBut you'd probably only get hung up on that being "magic" if your mental model of programming assumed stack-based scope for everything. Besides, how about this pseudocode? class Foo { field x; constructor(x) { this.x = x; } fun add(y) { return x + y; } } bar = Foo(3); bar.add(5) // returns 8, wait how did the x in the object remember the 3?
- jacquesm 16y agoStack based 'mental' scope is one of the bigger things to 'unlearn'. When I look at a function I build up a mental image of what is going on on the stack as the function executes. Things that I don't see declared within the function or that are explicitly allocated update the heap. To get away from that image is very hard to do at first. I compare it to learning real life languages, like English versus German. If you've never seen a language that has declinations in the endings of words then it can completely overwhelm you. Likewise tonal languages are strange when you've only spoken English or German. Each of those opens up the perspective you had before you learned that language to show you that there is more under the sun than what you thought was 'normal', and that your way is not always the better one. In programming languages it is much the same way.
- silentbicycle 16y agoIndeed. For some reason, closures never tripped me up, but continuations took a while to wrap my head around. (I think because I tried to learn continuation-passing-style first. While also learning OCaml.) Since you know C, it might help to think of continuations in terms of setjmp/longjmp, but with multiple, persistent stacks. (Same with coroutines, but coroutines and continuations have a lot of overlap.)
- jacquesm 16y ago> in terms of setjmp/longjmp, but with multiple, persistent stacks. That's exactly the thing I used to help me visualize it :) Funny you came up with the exact same model. Setjmp and longjmp are by the way probably two of the most abused calls in the C runtime package. But when you need them, you need them badly.