1

I can't help but notice that it's impossible to do something like

["cat", "dog"].map(String.prototype.toUpperCase);

which throws

Uncaught TypeError: String.prototype.toUpperCase called on null or undefined

The following code works (in es6 arrow notation for my typing convenience), but seems weirdly indirect:

["cat", "dog"].map(s=>s.toUpperCase())

Weirdly indirect because it creates an extra anonymous function just to wrap a perfectly good function that already exists. And maybe this is something that one must live with, but it doesn't taste right.

So I have two questions:

  1. Is there a direct way to map a string method over an array of strings without wrapping it in an anonymous function?

  2. What's the technical explanation for why the code I tried first doesn't work? I have two guesses, but don't know which is right.

Guess (a): this is because of boxing, i.e., string literals aren't the same as string objects, and for some reason mapping the method over them doesn't do the same kind of quiet coercion that just calling the method on the string does.

Guess (b): this is because while functions are first class objects, methods aren't in the same way, and can't ever be mapped?

Guess (c): there's some other syntax I should be using?! ("".prototype.toUpperCase??) Because of JavaScript gremlins? Because null both is and is not an object? Fixed in ES2025? Just use lodash and all will be cured?

Thanks!

1

4 Answers 4

5

1. Is there a direct way to map a string method over an array of strings without wrapping it in an anonymous function?

No (assuming that by "without wrapping it in an anonymous function" you more generally mean "without creating an additional function").

2. What's the technical explanation for why the code I tried first doesn't work? I have two guesses, but don't know which is right.

The "problem" is that toUpperCase is essentially an instance method. It expects this to refer to the value that should be changed. .map however will pass the value as argument to the function. Your first example is basically doing

String.prototype.toUpperCase("dog")

and that's simply not how that function works. The following would work

 String.prototype.toUpperCase.call("dog")

but that in turn is not how .map works.

No to guess a and b. Regarding c, the syntax you should be using is your second solution. Of course there are other ways. You could write a helper function:

function toUpper(s) {
  return s.toUpperCase();
}

but that's not much different from using the arrow function.

Since toUpperCase doesn't accept arguments, you could even go crazy and to something like

["cat", "dog"].map(String.prototype.toUpperCase.call.bind(String.prototype.toUpperCase));

but that

  • also creates a new function
  • is probably less understandable
  • is not generally applicable
Sign up to request clarification or add additional context in comments.

3 Comments

Actually this approach works without creating an additional function (though I wouldn't recommend it over the clear and concise arrow function)
Aah, thank you. That clears things up. (I mostly write functional code, so I'm not used to thinking about this or about methods as anything other than functions with weird syntax.)
@Bergi: Ah, clever :)
2

Object methods (and dealing with this) can be a real pain in the neck when you're trying to write elegant functional programs. No worries tho, just abstract the problematic syntax away into its own function

const send = (method, ...args) => obj =>
  obj[method](...args)

let x = ["cat", "dog"].map(send('toUpperCase'))
console.log(x) // ['CAT', 'DOG']

let y = ['cat', 'dog'].map(send('substr', 0, 1))
console.log(y) // ['c', 'd']

Here's another way that might be nice to write it

const call = (f, ...args) => obj =>
  f.apply(obj, args)

let x = ["cat", "dog"].map(call(String.prototype.toUpperCase))
console.log(x) // ['CAT', 'DOG']

let y = ['cat', 'dog'].map(call(String.prototype.substr, 0, 1))
console.log(y) // ['c', 'd']

And another way by defining your own functions

const map = f => xs => xs.map(x => f(x))
const toUpperCase = x => x.toUpperCase()
const substr = (x,y) => z => z.substr(x,y)

let x = map (toUpperCase) (["cat", "dog"])
console.log(x) // ['CAT', 'DOG']

let y = map (substr(0,1)) (['cat', 'dog'])
console.log(y) // ['c', 'd']

4 Comments

That send function is pleasingly elegant. Part of me wants to just use that to stomp all over the global map method and write something like Array.prototype.map(f) = try {this.map(f)} catch {this.map(send(f))}
You definitely don't want to use try/catch for control flow. Please, please don't do that
heh. is there some other good way to detect whether the function passed in is a real function or a method?
There's no such thing as a method. We just use that word to describe a function that has its context dynamically bound to an object. I added another technique you can use in an update to my answer.
0

To answer both your questions, No you can't just map a string method to an array of strings without using a callback function.

And the first code you wrote:

["cat", "dog"].map(String.prototype.toUpperCase);

Doesn't work because .map() method expects a callback function that process every iterated element and return it, but you were just trying to call String.prototype.toUpperCase function on undefined because you haven't binded it to the current element.

Solution:

In order to get it to work and to be equivalent to ES6 provided code:

["cat", "dog"].map(s=>s.toUpperCase())

You need to amend your code to accept a callback function that calls String.prototype.toUpperCase on the iterated element which is passed by .map() method:

["cat", "dog"].map(function(s){return s.toUpperCase()});

Comments

0

var a = ["cat", "dog"];

var res = a.map(String.prototype.toUpperCase.call, String.prototype.toUpperCase);

console.log(res);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.