Our friend Scope

Scope is one of those things that is absolutely essential to understand as a programmer. If you don’t understand how a language handles scope, you are doomed to continue making difficult-to-understand mistakes and poor code design.

Scheme is a good platform for exploring how scope works for beginners, and the language features that it has distilled to the minimum form the basis of 99% of the cases you encounter from day to day.

Let’s look at one pattern that you’ll find in every jQuery plugin:

[javascript]
(function ($) {
// some jQuery code
})(jQuery)
[/javascript]

What does it do? It temporarily binds the variable $ to jQuery only inside the function body. Clever trick? Well… no, because this is familiar to anybody who understands Scheme. Let’s look at the Scheme equivalent:

[scheme]
((lambda ($) some-code) jQuery)
[/scheme]

Oh, that looks familiar. It is just the elementary equivalency for let:

[scheme]
(let (($ jQuery))
code)
[/scheme]

Side effects

Here’s another JavaScript, jQuery example:

[javascript]
var i
for(i = 0; i < 5; i++) {
$("#container-div").append(
$("<button type=’button’>Click me!</button>")
.click(function() {
alert(i)
})
)
}
[/javascript]

Before you click, guess what each button will display!

container-div:


If you guessed correctly, give yourself a pat on the back. The simple explanation here is that you only ever create one variable i, so you can’t have more than one value for i.

Let’s fix that by introducing a new variable k inside the loop, so that each alert gets bound to a new k.

[javascript]
var i
for(i = 0; i < 5; i++) {
var k = i
$("#container-div").append(
$("<button type=’button’>Click me!</button>")
.click(function() {
alert(k)
})
)
}
[/javascript]

Before you click, guess what each button will display!

container-div2:

Translating Javascript to Scheme

We might better understand this in Scheme.

Begin by moving all var declarations to the top of their function body while keeping their references intact:

[javascript]
var i
var k
for(i = 0; i < 5; i++) {
k = i
$("#container-div2").append(
$("<button type=’button’>Guess again!</button>")
.click(function() {
alert(k)
})
)
}
[/javascript]

The var declarations at the top are just Scheme internal defines that we wrap in a blank let:

[scheme]
(let ()
(define i (void))
(define k (void))
;; for loop…
)
[/scheme]

The internal defines expand into a let statement, and the wrapper let is no longer needed:

[scheme]
(let ((i (void))
(k (void)))
;; for loop …
)
[/scheme]

We’re just going to unroll this loop and convert the jQuery stuff into some pseudo-code. It would be wrong to convert this loop into a recursive program.

[scheme]
(let ((i (void))
(k (void)))
(set! i 0)
(set! k i)
(append! container-div2
(make-button "Guess again!"
(lambda ()
(alert k))))
(set! i 1)
(set! k i)
(append! container-div2
(make-button "Guess again!"
(lambda ()
(alert k))))
(set! i 2)
(set! k i)
(append! container-div2
(make-button "Guess again!"
(lambda ()
(alert k))))

)
[/scheme]

You see that there is only ever one i and one k, and that we are simply mutating their values. Furthermore, lexical scope tells us that the free vars k in each lambda capture a that r, but doesn’t make a copy. The use of set! should also ring warning bells, especially with its “!” suffix.

Let’s fix that with a let:
[scheme]
(append! container-div2
(make-button "Guess again!"
(let ((k k))
(lambda ()
(alert k)))))
[/scheme]

What this does is extend the environment with a new k (that has the value of the old k) only for the body of that lambda. This way, we make sure we capture the value of k as it was at that point in time.

Javascript doesn’t have let, but it does have first-class closures, so let’s translate this let into lambdas:

[scheme]
(append! container-div2
(make-button "Guess again!"
((lambda (k)
(lambda ()
(alert k))) k)))
[/scheme]

and back into Javascript:

[javascript]
$("container-div2").append(
$("<button type=’button’></button>")
.click((function (k) {
return function () {
alert(k)
}
})(k))
)
[/javascript]

and it does work as expected:

container-div3:

A moral?

Don’t put var declarations inside loops. They’re deceptive. Also, don’t trust the language to automatically generate new instances of things for you. Make it explicit.

Leave a Reply