Adding .map() directly to String prototype
In fact there should be a common interface that iterables should implement. I think this could have been proposed before. This way there would be not more common methods proposals. On may. 17 2018, at 10:16 am, Ben Fletcher <bfletch at gmail.com> wrote:
Consider adding the array .map() implementation directly to the String prototype.
Here is a possible polyfill:
String.prototype.map = function (fn) { return Array.prototype.map .call(this, fn) .join(''); }
This replaces the common pattern of splitting, mapping, joining strings for processing.
This idea comes from a rather ASCII-centric perspective upon strings and will definitely break multi-byte characters. And even if it wouldn't, your intent might have been to do just that.
Long story short, it's debatable what exactly you want to iterate in a string, hence such a method would lead to pretty bad practice and should therefore not be added to the language.
On Thu, May 17, 2018 at 4:24 PM, kdex <kdex at kdex.de> wrote:
This idea comes from a rather ASCII-centric perspective upon strings and
will
definitely break multi-byte characters.
No. One of the improvements to JavaScript since ES5 (you know, those things you keep objecting to) is that strings are now iterable, and the iteration visits characters (codepoints), not code units. (Example: jsfiddle.net/03tLpbrv) (Also worth noting that JavaScript strings were never ASCII-centric. They were UTF16-centric [with invalid surrogate pairs tolerance].)
If there were a .map
added, it would presumably visit characters, not
code units, as well; it would be the only reasonable thing to do, both in
light of how strings iterate and just general utility/appropriateness.
-- T.J. Crowder
On Thu, May 17, 2018 at 4:16 PM, Ben Fletcher <bfletch at gmail.com> wrote:
Consider adding the array .map() implementation directly to the String
prototype.
Here is a possible polyfill:
String.prototype.map = function (fn) { return Array.prototype.map .call(this, fn) .join('');
Recommend something using the string's iterator instead, since the version above will visit code units, not codepoints (characters). Crude unoptimized version (jsfiddle.net/dkty51ys):
Object.defineProperty(String.prototype, "map", {
value: function(fn, thisArg = undefined) {
let result = "";
for (const ch of this) {
result += fn.call(thisArg, ch);
}
return result;
}
});
or via conversion to array and back (jsfiddle.net/r0uqgx65):
Object.defineProperty(String.prototype, "map", {
value: function(fn, thisArg = undefined) {
return [...this].map(fn, thisArg).join("");
}
});
This replaces the common pattern of splitting, mapping, joining strings for processing.
Is it really that common? If so, the footgun available (iterating code units rather than codepoints), could be an argument in favor...
-- T.J. Crowder
Combining marks will continue to be an issue (as they are with iteration), but I'd expect that code dealing with languages for which combining marks are a significant thing wouldn't be doing this sort of mapping in the first place, or will have language-specific rules for handling mapping with combining marks. If it's a pain point for someone, they can always propose a locale-specific mapping function standard, perhaps for ECMA-402...
-- T.J. Crowder
On Thu, May 17, 2018 at 11:16 AM, Ben Fletcher <bfletch at gmail.com> wrote:
Consider adding the array .map() implementation directly to the String prototype.
There is a big difference between mapping over an array and mapping
over a string. Arrays can hold anything. Strings hold only
characters. So for a function that takes something of type A and
returns something of type B, map
ping it over an array takes an array
of As and returns an array of Bs.
['a', 'b', 'c'].map(c => c.charCodeAt(0)) //=> [97, 98, 99]
But since a String only holds characters, we can't make it properly
hold the results of c => c.charCodeAt(0)
:
'abc'.map(c => c.charCodeAt(0)) //=> "979899"
reduce
would be a bit different, as it can accumulate to any type.
I think map
would be a bad idea here.
Yeah; I am aware that ES2015 added measures that make it possible iterate over codepoints, but that was not quite my point. The problem that I see is that in some scenarios it makes sense to think of a string as an array of bytes, and sometimes you need to iterate over a string in terms of its codepoints. Some might even want to iterate in terms of visible glyphs, taking combining marks into account.
This ambiguity makes String.prototype.map
moot, as it remains questionable
what exactly should be iterated. Bytes? Codepoints? Entire glyphs?
What's the use case where you'd want to map over every (character / code point / grapheme / whatever) in a string, and apply the same callback logic to each one?
We do this on our front end where we replace certain emojis with our custom brand icons.
I don’t support this suggestion, though.
I agree map is not really a good fit for strings but this looks like yet
another use case for ::
operator
const map = [].map;
str::map(codeUnits);
[...str]::map(codePoints);
Why didn't that proposal made it again? It would make every request to add prototypal methods redundant!
We do this on our front end where we replace certain emojis with our
custom brand icons.
Seems like something .replace
can already do for you?
And to my understanding, .replace
can be better optimized for that
use case - it can limit graph copying when it builds the new string,
among other things.
If they actually need something like the .map
as proposed here, they
could just as easily do str.replace(/./gsu, m => ...)
, or if they're
stuck with non-transpiled ES2015, str.replace(/[^]/gu, m => ...)
.
Isiah Meadows me at isiahmeadows.com, www.isiahmeadows.com
Consider adding the array .map() implementation directly to the String prototype.
Here is a possible polyfill:
This replaces the common pattern of splitting, mapping, joining strings for processing.