1

First off, I read the related articles on this and felt they were unsuitable to me since I am using a series of methods I am inheriting and not a few arithmetic functions I can easily change. Secondly, googling 'javascript chaining method' has results mostly concerning jQuery and D3. The JavaScript chain method is surprisingly under-documented it seems.

I am trying to implement underscore in CoffeeScript. I am working on the _.chain method. I am decorating underscore methods to newObject and then returning it. I have a private variable called value which is suppose to be the value that is modified by chained method calls. Calling getValue() on the returned newObject should return the private value variable.

For some reason though the private value variable is not changing when I call a method on newObject. The underscore methods are being curried correctly, but they do not change when a function is invoked. I tried reading the underscore source code but it seems they architect there chain method in a completely different way that I cannot duplicate without restructuring the way my underscore object is constructed.

_.chain

_.chain = (v) ->
  newObj = {}
  value = v
  newObj.getValue = -> value
  for name, fn of _
    if typeof fn is 'function'
      newObj[name] = (callback, otherVal) ->
        value = fn(value, callback, otherVal)
        newObj
  newObj

Example:

var a = _.chain([1,2,3,4]);
a.map(function(value) {
  return value + 5;
})
a.getValue().getValue()
>>>[1, 2, 3, 4]
Community
  • 1
  • 1
chopper draw lion4
  • 12,401
  • 13
  • 53
  • 100
  • 1
    @Mathletics Implementing something yourself is a great way to understand how said thing works. A better question to ask: why do you need to call `getValue` twice? I'd bet that the reason you need to do that is the same reason the function isn't working. – Matt Wetmore Apr 18 '15 at 22:50

1 Answers1

1

The problem is related to the behaviour of closures (this is javascript after all). The problem shows up here:

newObj[name] = (callback, otherVal) ->
  value = fn(value, callback, otherVal)
  newObj

The value of fn is closed over, to be used whenever the function associated with name is called on newObj. The problem is that the value of fn changes throughout the lifecycle of the chain function, and so the closed-over value will change as well. In particular, after chain exits, the value of fn in the closures for each underscore function you've attached to newObj will be whatever function is iterated last in the for name, fn of _ loop. In this case, that function is chain, so when you are calling map in your example, when the interpreter hits the line value = fn(value, callback, otherVal), fn is the chain function.

This is why you need to call getValue twice in your example: a.map(...) is calling chain internally, so it returns a chained, chained object.

In order to fix this, we can use an IIFE so that instead of closing over fn, the functions you attach to newObj close over the actual function you want:

newObj[name] = ((f) ->
  (callback, otherVal) ->
    value = f(value, callback, otherVal)
    newObj
  )(fn)

Now, instead of closing over fn to be used later (and later, it won't be the fn we want), we close over f, which we immediately bind to be the current value of fn.

Here is the full fixed version, in Coffeescript:

_.chain = (v) ->
  newObj = {}
  value = v
  newObj.getValue = -> value
  for name, fn of _
    if typeof fn is 'function'
      newObj[name] = ((f) ->
        (callback, otherVal) ->
          value = f(value, callback, otherVal)
          newObj
      )(fn)
  newObj

You can check it with your example and see that it gives the correct answer now.

Matt Wetmore
  • 300
  • 2
  • 5