-1

I'm learning functional programming and node.js, and I came across this odd problem when using Function.prototype.apply and .bind.

function Spy(target, method) {
    var obj = {count: 0};
    var original = target[method]
    target[method] = function (){//no specified arguments
        obj.count++
        original.apply(this, arguments)//only arguments property passed
    }
    return obj;
}
module.exports = Spy

This code works, it successfully spies on target.method.


//same code here
    target[method] = function (args){//args specified
        obj.count++
        original.apply(this, args)//and passed here
    }
//same code here

This code, however, does not. It gives an error message: TypeError: CreateListFromArrayLike called on non-object.


And then the biggest surprise is, this method works perfectly fine.

//same code here
    target[method] = function (args){
        obj.count++
        original.bind(this, args)
    }
//same code here

So why exactly do I get this error? Is it because function arguments are not necessarily objects? Or is it because apply has a stricter description than bind?

melpomene
  • 79,257
  • 6
  • 70
  • 127
Attila Herbert
  • 309
  • 1
  • 2
  • 10
  • 3
    Well, yes, [`apply`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply) and [`bind`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) have very different descriptions. `apply` takes an array or an [`arguments` object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments) as its second parameter - if you don't pass one, it complains. – Bergi Aug 20 '16 at 21:37
  • Very unclear. First, the works/doesn't work code fragments are identical (unless I'm missing something). Second, `bind` doesn't invoke the function, so it's hard to believe that "works". – Amit Aug 20 '16 at 21:39
  • @Amit sorry, I'm going to edit so it's more apparent. – Attila Herbert Aug 20 '16 at 21:44
  • 1
    It seems like your're [confusing `apply` with `call`](http://stackoverflow.com/q/1986896/1048572) – Bergi Aug 20 '16 at 21:48
  • 1
    …and regarding `bind`, [it just does something completely different](http://stackoverflow.com/q/15455009/1048572). – Bergi Aug 20 '16 at 21:49
  • `arguments` is an array (-ish) containing all arguments. In `function (args)`, `args` is just the name of the first parameter. – melpomene Aug 20 '16 at 22:01
  • what about `original(args);`? – Washington Guedes Aug 20 '16 at 22:03
  • @Bergi I'm not confusing `apply` with `call`. I need to call the function with more than one arguments. I tried with `bind` as an "experiment" basically. And it surprisingly worked, that's why I asked this question. – Attila Herbert Aug 21 '16 at 00:47
  • @AttilaHerbert: If you need multiple arguments, then why do you only pass the single parameter `args`? And no, `bind` doesn't work at all, since it's not calling the function. – Bergi Aug 21 '16 at 10:43

2 Answers2

1

bind is called the same way as call is, even though they do very different things.

If you really wanted to use bind in this way. You could use the spread operator (ES2015) to expand the arguments 'array' to individual arguments:

original.bind(null, ...args);

That will bind the original function with the array values as individual arguments.

Snivels
  • 11
  • 2
  • 1
    I have no idea why you bother to use slice when you already have spread operator. Wet monkeys. – Sylwester Aug 20 '16 at 22:24
  • @Sylwester This is true, I completely forgot that you can use the spread operator on arrays and array-like objects as well. Removed the conversion before use. – Snivels Aug 20 '16 at 22:47
1

In this version:

target[method] = function (args){//args specified
        obj.count++
        original.apply(this, args)//and passed here
    }

Here you are not taking all the arguments but just one, named args. Since apply expects an array like object you cannot use args since it is only the first argument passed to the original target.

You can change it to:

target[method] = function (arg){   //only one argument specified
        obj.count++
        original.apply(this,[arg]) //one argument passed here
}

Now it works, but you can only spy on one argument functions. Using call would be better since you only have one extra argument:

target[method] = function (arg){ //only one argument specified
        obj.count++
        original.call(this,arg)  //one argument passed here
}

Now bind is a totally different animal. It partial applies functions, thus return functions. Imagine you need to send a callback that takes no arguments but calls a function with some arguments you have when making it. You see code like:

var self = this;
return function() {
  self.method(a, b);
}

Well. bind does this for you:

return this.method.bind(this, a, b);

When calling either of these returned functions the same happens. The method method is called with the arguments a and b. So calling bind on a function returns a partial applied version of that function and does not call it like call or apply does.

Sylwester
  • 44,544
  • 4
  • 42
  • 70
  • Bind does not curry. If at all, it does partial application, but not even that is its main purpose. – Bergi Aug 21 '16 at 10:45
  • @Bergi You're right partial application is a better term for eager languages, but I think you are wrong about the "main" purpose of `bind` as it is exactly what it does in a `call` context. – Sylwester Aug 21 '16 at 11:50