_ = require 'underscore'
{ok, deepEqual, throws} = require 'assert'
eq = deepEqual
Lexical scoping is the grouping of names with values according to the surrounding source code. Lexical scope refers to the visibility of a variable and its value analogous to its textual representation.
v = 'outer'
f = ->
v = 'middle'
[1, 2, 3].map (i) ->
v = 'inner'
[v, i].join(' ')
eq f(), ["inner 1", "inner 2", "inner 3"]
Dynamic scoping is built on the idea of a global table of named values.
globals = {}
Maintaining a global map of stacks associated with binding names is the core of dynamic scoping.
makeBindFunc = (resolver) ->
(k, v) ->
stack = globals[k] || []
globals[k] = resolver(stack, v)
globals
Now let's add a few policies for adding bindings to our global table.
The function bind
performs a very simple task: it takes a key and
a value and pushes the value onto the global bindings map at the slot
associated with the key:
bind = makeBindFunc (stack, v) ->
stack.push(v)
stack
The unbind
function is the antithesis of bind
in that it pops the
last value binding off of the stack associated with a name:
unbind = makeBindFunc (stack) ->
stack.pop()
stack
Finally, we’ll need a function to look up bound values:
lookup = (k) ->
_.last globals[k]
bind 'a', 1
bind 'b', 1000
The lookup
function provides a convenient way to look up the value at
the top of a named value binding stack, and is used to simulate this reference
resolution. Note the effects that various operations have on the simulated
dynamic scope:
ok lookup('a') is 1
ok lookup('b') is 1000
ok lookup('c') is undefined
eq globals,
a: [1]
b: [1000]
In a dynamic scoping scheme, the value at the top of a stack in a binding is the current value:
bind 'a', '*'
ok lookup('a') is '*'
eq globals,
a: [1, '*']
b: [1000]
To retrieve the previous binding is as simple as unbinding by popping the stack:
unbind 'a'
ok lookup('a') is 1
eq globals,
a: [1]
b: [1000]
You may already imagine (or know) how a scheme like this (i.e., the manipulation of global named stacks) may cause trouble, but if not observe the following:
f = -> lookup 'a'
ok f('a') is 1
g = ->
bind 'a', 2
f()
ok g('a') is 2
ok f('a') is 2
eq globals,
a: [1, 2]
b: [1000]
Here we see that though f
never manipulated the binding of a
, the value
that it saw was subject to the whim of its caller g
! This is the poison of dynamic scoping: the value of any given binding cannot be known until the caller of any given function is known — which may be too late.
The this
(or @
) reference can point to different values depending on the
context in which it was first created, but it’s actually much worse than that.
Instead, the value of @
, like our binding of a
, is also determined by
the caller:
f = -> @
nums = [1, 2, 3]
eq f.call(nums), nums
eq f.apply(nums, []), nums
Yep, the value of the this reference is directly manipulable through the use of
apply
or call
. That is, whatever object is passed into them as the first argument becomes the referenced object.
Underscore's bind
function allows you to prevent the @
reference from
changing:
letters = ['a', 'b', 'c']
bound = _.bind f, letters
eq bound.call(nums), letters
You can use the bindAll
function to lock @
to a stable value
for all of the named methods:
target =
a: 'yes!'
b: -> @a
c: -> @b()
throws -> target.c.call('no!') # throws an error
_.bindAll target, 'b', 'c'
ok target.c.call('no!') is 'yes!'
A closure is a function that captures the external bindings (i.e., not its own arguments) contained in the scope in which it was defined for later use (even after that scope has completed).
Local variables are not the only things that can be captured. Function arguments can be captured as well:
shoutTo = (name) ->
(say) -> "#{say} #{name}!"
shoutToJoe = shoutTo 'Joe'
ok shoutToJoe('Hi') is 'Hi Joe!'
ok shoutToJoe('Bye') is 'Bye Joe!'
Another example:
scaleBy = (factor) ->
(nums) ->
n * factor for n in nums # nums.map (n) -> n * factor
scaleByTen = scaleBy 10
eq scaleByTen([1, 2, 3]), [10, 20, 30]
Note how the variable factor
is retained within the body of the returned scaling function and is accessible anytime that function is called. This variable retention is precisely the definition of a closure.
So how would we simulate a closure using our function-scoped this scratchpad from the previous section? First of all, I’ll need to devise a way for capturing closed variables while simultaneously maintaining access to non-closed variables normally. The most straightforward way to do that is to grab the variables defined in the outer function individually and bind them to the this of the returned function, as in the following:
scaleBy = (factor) ->
(nums) ->
@FACTOR = factor
context = @
boundFunc = _.bind ((n) -> n * @FACTOR), context
nums.map boundFunc
scaleByTen = scaleBy 10
eq scaleByTen([1, 2, 3]), [10, 20, 30]
eq scaleByTen.call({}, [1, 2, 3]), [10, 20, 30]
A free variable refers to variables used in a function that are not local variables nor parameters of that function
Free variables are related to closures in that it is the free variables that will be closed over in the creation of said closure. The basic principle behind closures is that if a function contains inner functions, then they can all see the variables declared therein; these variables are called “free” variables. However, these variables can be captured and carried along for later use in inner functions that “escape” from a higher-level function via return. The only caveat is that the capturing function must be defined within the outer function for the capture to occur. Variables used in the body of any function without prior local declaration (neither passed into, nor defined locally) within a function are then captured variables.
addTo = (x) ->
(y) -> y + x # x is captured in the returned function
addToTen = addTo 10
ok addToTen(2) is 12
The variable x
in the outer function is indeed captured in the returned
function since the inner function never declares x
, but refers to it anyway.
Thereafter, the function returned from addTo
retains the value of the
variable x
captured when it was created (viz., 10 in the example above) and uses it in its calculations.
Creating another addTo
function will capture the same named variable x
but with a different value, because it will be created during a later invocation of
addTo
.
addToNine = addTo 9
ok addToNine(2) is 11
The value captured can be of any type, including another function. The
following function, avgDamp, captures a function and returns a function that calculates the average of some value and the result of passing it to the captured function. In other words, given a function f
, we consider the function whose value at x
is equal to the average of x
and f(x)
.
avg = (nums) ->
total = nums.reduce (prev, next) -> prev + next
total / nums.length
avgDamp = (f) ->
(x) -> avg [x, f(x)]
avgSq = avgDamp (x) -> x * x
ok avgSq(10) is 55
complement = (pred) ->
(args...) ->
not pred(args...) # pred is captured in returned func
isEven = (x) -> x % 2 is 0
isOdd = complement isEven
ok isEven(2)
ok isOdd(5)
ok not isOdd(2)
ok not isEven(5)
The function get
takes a key into an associative structure (such as an
array or an object) and returns a function that, given a structure, returns the
value at the key.
get = (key) ->
(obj) -> obj and obj[key]
book =
title: 'Moby Dick'
author: 'Melville'
titleOf = get 'title'
ok titleOf(book) is 'Moby Dick'
We can also use get
to return a function that takes an index as key
and in
turn then returns the value at the specified index when passed a list.
taocp =
author: 'Knuth'
title: 'TAOCP'
stars: 5
books = [
author: 'Sussman and Abelson'
title: 'SICP'
stars: 5
,
author: 'Kernighan and Ritchie'
title: 'The C Programming Language'
stars: 4
,
author: 'Knuth'
title: 'TAOCP'
stars: 5
]
thirdItemIn = get 2
eq thirdItemIn(books), taocp
The get
function comes in handy in conjunction with filter
, which can be
used to grab objects in an array with a certain field.
rating = get('stars')
result = books
.filter((x) -> rating(x) > 4)
.map(get('title'))
eq result, ['SICP', 'TAOCP']