String.prototype.join

The equivalent of ['a', 'b'].join(', ') in Python is ", ".join(["a", "b"]). My first impression of this, many years ago, was that this looked silly. However, I believe it to be a strictly better way of joining parts by a string.

I believe the String prototype is a more logical owner of a join method because the common denominator for things that can be joined by a string, is that they are iterable, and not that they are Arrays.

Since the advent of spread operator, joining iterables is trivial ([...mySet].join(", ")).

However, I think Python got this technically correct, and think it could be a useful addition to ECMAScript.

Polyfill implementation

String.prototype.join = function join(iter) {
  return [...iter].join(this);
};

Example

String.prototype.join = function join(iter) {
  return [...iter].join(this);
};

const myArr = ["a", "b", "c"];
const mySet = new Set(["a", "b", "c"]);
const myMap = new Map([
  [1, "a"],
  [2, "b"],
  [3, "c"],
]);

console.log(", ".join(mySet));
// a, b, c

console.log(", ".join(myArr));
// a, b, c

console.log(", ".join(myMap.keys()));
// 1, 2, 3

console.log(", ".join(myMap.values()));
//a, b, c

Another option would be to put the join() method on all iterators (via the iterator helpers proposal).

Maybe. I am thinking that joining all value together to a string makes only sense if everything is buffered already kind of. I like what you are thinking, but I feel like this is more suitable on the String prototype.

The only drawback I can really see, is that it is not possible to do "1,2,3".split(",").join("-") with the Python variant. But I am not suggesting Array.prototype.join is removed.

I think the most compelling reason to add String.prototype.join is if there is possibilities of performance improvements under the hood. But they might be possible, or is already done, in the optimizers using [...myIter].join(",").

@theScottyJam, or what is your take on this?

1 Like

I remember disliking the fact that Python's join method seemed to be flipped around, I never knew why they did it until you explained it here, and I can see the wisdom in that. But still, it's conceptually backwaords. You're "joining" the array (or iterator) together with a string. This means you're acting on the array/iterator, and the string is just a parameter. I don't believe Python has a way to arbitrarily attach methods to iterators, so the only way they can make join support all iterators is to reverse it and make join an instance method on the string, or, they could have just made join a static method on their list class (or some new iterator namespace) and added join to that and had join take two arguments, I would have prefered that over the reversal they did.

Anyways, to address some of your feedback

Maybe. I am thinking that joining all value together to a string makes only sense if everything is buffered already kind of. I like what you are thinking, but I feel like this is more suitable on the String prototype.

I'm not exactly sure what you mean here. If it's able to consume an arbitrary iterator, then there's going to be no guarantee that it's buffered, even in Python's case.

>>> f = open('./temp.txt', 'r')
>>> '\n===\n'.join(f) # This file object is not buffered. Python is reading the file as it joins.
'hi\n=====there\n'

The only drawback I can really see, is that it is not possible to do "1,2,3".split(",").join("-") with the Python variant.

We won't need to worry about fluent-API support once the pipeline operator proposal gets through. You would be able to write that like this if you don't want to use the array version:

"1,2,3"
  |> %.split(",")
  |> %.join("-")

I think the most compelling reason to add String.prototype.join is if there is possibilities of performance improvements under the hood.

Adding it to the iterator prototype would cause it to have the same performance optimizations. For example, I would be able to write something like this, and the engine will be able to build the joined string as it consumes the iterator, without having to build an intermediet array.

function* upTo(limit) {
  for (let i = 0; i < limit; ++i) yield i
}

// join() is being called on the iterator produced by the generator.
// It's not being called on an array.
upTo(100).join(',')

Of course, if we want to go this route, we would need to request that they add this function to the collection of functions they're adding to the iterator. Who knows, maybe functions like this could end up becoming a follow-on proposal instead of part of the core.

1 Like