0

Let's say that I want to create a function that trims its input, one way I can do it is simply

x => x.trim()

Another way would be

x => String.prototype.trim.call(x)

Which has the advantage that if x has defined an override for the .trim(), I can still grab the implementation from the prototype. By extension, if I want to use the same things in a .map() I can do either,

[" foo", "bar "].map(x => x.trim())
[" foo", "bar "].map(x => String.prototype.trim.call(x));

I wante do make a pointfree function though. What I don't understand is why can't use a uninvoked function created with .call

[" foo", "bar "].map(String.prototype.trim.call);

I also tried .apply which because there are no arguments is effectively the same thing.

Likewise, outside of a map you also can't use the function .call function

> fn = String.prototype.trim.call
[Function: call]
> fn('asdf')
TypeError: fn is not a function

So my questions are,

  1. In the above, the function returned by String.prototype.trim.call does what exactly? What kind of function is that?
  2. Why doesn't my attempt with .call work?
  3. Is there anyway to do this with .bind, .apply, or .call?
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • 1
    `map` passes additional arguments. It passes the item, the index of that item and the array. – Dan D. Dec 14 '18 at 02:40
  • 1
    see https://stackoverflow.com/questions/9304007/why-wont-passing-trim-straight-to-maps-callback-work – CertainPerformance Dec 14 '18 at 02:42
  • I know that the JS community tends to rely on reference identity but still you might want to consider `invoke = (name, ...args) => o => o[name] (...args)` –  Dec 14 '18 at 09:07
  • Possible duplicate of [Why won't passing \`''.trim()\` straight to \`\[\].map()\`'s callback work?](https://stackoverflow.com/questions/9304007/why-wont-passing-trim-straight-to-maps-callback-work) – tevemadar Dec 14 '18 at 20:29
  • this has nothing to do with trim.. that's just an example. – Evan Carroll Dec 14 '18 at 20:46

2 Answers2

2

What I don't understand is why can't use a uninvoked function created with .call

[" foo", "bar "].map(String.prototype.trim.call);

The problem here is that all functions inherit the same single call method from Function.prototype; so the above is equivalent to

[" foo", "bar "].map(Function.prototype.call);

For your code to work, call would need to "remember" that you got it from String.prototype.trim and not from some other function; but that's simply not how method calls work in JavaScript. In a method-call expression like foo.bar(), foo isn't just the object whose bar property gets invoked as a function, it's also the object that gets implicitly passed as this to that function. In the case of String.prototype.trim.call(x), the only reason call knows to invoke String.prototype.trim is that you're using method-call syntax, so it can get it from this.


  1. In the above, the function returned by String.prototype.trim.call does what exactly? What kind of function is that?

It's a method that takes an object and zero or more arguments, and that, when called on a function, invokes that function as a method on that object, passing those arguments.

  1. Why doesn't my attempt with .call work?

Because it tries to invoke call as a free function instead of as a method; so call doesn't receive a this argument telling it what function to invoke, so it can't do its job. (It will most likely receive the "default object", e.g. window, as its this argument; but there are some subtleties that I'm not sure of. You can try [" foo", "bar "].map(function () { return this; }) in your testbed to see what it gives you in your environment.)

  1. Is there anyway to do this with .bind, .apply, or .call?

Yes; you can write:

[" foo", "bar "].map(Function.prototype.call.bind(String.prototype.trim))

where Function.prototype.call.bind(String.prototype.trim) is a function that, when called, invokes call on String.prototype.trim. (In other words, bind handles "remembering" that String.prototype.trim is the object that you want to pass as this.)

That said, I really think your original version,

[" foo", "bar "].map(x => x.trim())

is vastly superior. If someone has overridden trim(), you should trust that it was for a good reason, and that you should indeed call that override instead of forcing the use of the inherited one instead.

ruakh
  • 175,680
  • 26
  • 273
  • 307
1

Interesting question! It boils down to the signature of Array.prototype.map:

var new_array = arr.map(function callback(currentValue[, index[, array]]) {
    // Return element for new_array
}[, thisArg])

The key to understanding the behavior you noticed is the optional thisArg - it's the context that will be supplied to the callback

Furthermore, from the spec:

If a thisArg parameter is provided to map, it will be used as callback's this value. Otherwise, the value undefined will be used as its this value.

So that meand that undefined will be used as the context for the callback, which in this case is String.prototype.trim.call

Here's where it gets interesting... the normal context for Function.prototype.call (which String.prototype.trim.call inherits) is the function that it calls, i.e. String.prototype.trim in this case. The first argument to Function.prototype.call provides the context to the function that will be called.

In the case of << Array >>.map(String.prototype.trim.call), the context provided to String.prototype.trim.call is undefined, which means we wind up attempting to call undefined with a context of an element from the array. All clear? It's definitely a bit of a mind twist.

We can take advantage of this behavior to achieve the effect we desire by providing String.prototype.trim as thisArg in << Array >>.map(callback, thisArg) and using Function.call as the callback:

console.log([" foo", "bar "].map(Function.call, String.prototype.trim));

Another way to think of it is that String.prototype.trim provides the context to Function.call to produce a bound function, and then each element of the array provides the context to the bound function.

ic3b3rg
  • 14,629
  • 4
  • 30
  • 53