Skip to content

Latest commit

 

History

History
322 lines (218 loc) · 9.02 KB

3.coffee.md

File metadata and controls

322 lines (218 loc) · 9.02 KB

Variable Scope and Closures

_ = require 'underscore'
{ok, deepEqual, throws} = require 'assert'

eq = deepEqual

Lexical Scoping

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

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.

Javascript's Dynamic Scope

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!'

Closures

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]

Free Variables

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

Using Closures

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)

Closures as an Abstraction

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']