0

I'm doing array manipulation in Javascript, and I want to be able to chain operations with multiple calls to map, concat, etc.

const someAmazingArrayOperation = (list) => 
  list
    .map(transformStuff)
    .sort(myAwesomeSortAlgorithm)
    .concat([someSuffixElement])
    .precat([newFirstElement])
    .filter(unique)

But the problem I've run into is that Array.precat doesn't exist. (Think of Array.concat, but the reverse.)

I don't want to modify Array.prototype in my own code, for reasons. (https://flaviocopes.com/javascript-why-not-modify-object-prototype/)

I could totally use Array.concat and concatenate my array to the end of the prefix array and carry on. But that doesn't chain with the other stuff, and it makes my code look clunky.

It's kind of a minor issue because I can easily write code to get the output I want. But it's kind of a big deal because I want my code to look clean and this seems like a missing piece of the Array prototype.

Is there a way to get what I want without modifying the prototype of a built-in type?

For more about the hypothetical Array.precat, see also: concat, but prepend instead of append

Mnebuerquo
  • 5,759
  • 5
  • 45
  • 52

3 Answers3

6

You could use Array#reduce with a function which takes the initialValue as array for prepending data.

const
    precat = (a, b) => [...a, b],
    result = [1, 2, 3]
        .reduce(precat, [9, 8, 7]);

console.log(result)
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1

I don't want to modify Array.prototype in my own code, for reasons.

These reasons are good, but you can sidestep them by using a collision-safe property - key it with a symbol, not a name:

const precat = Symbol('precatenate')
Array.prototype[precat] = function(...args) {
  return [].concat(...args, this);
};

const someAmazingArrayOperation = (list) => 
  list
    .map(transformStuff)
    .sort(myAwesomeCompareFunction)
    .concat([someSuffixElement])
    [precat]([newFirstElement])
    .filter(unique);
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

If you don't want to modify Array.prototype, you can consider extends:

class AmazingArray extends Array {
    precat(...args) {
        return new AmazingArray().concat(...args, this);
    }
}

const transformStuff = x => 2*x;
const myAwesomeSortAlgorithm = (a, b) => a - b;
const someSuffixElement = 19;
const newFirstElement = -1;
const unique = (x, i, arr) => arr.indexOf(x) === i;

const someAmazingArrayOperation = (list) => 
  new AmazingArray()
    .concat(list)
    .map(transformStuff)
    .sort(myAwesomeSortAlgorithm)
    .concat([someSuffixElement])
    .precat([newFirstElement])
    .filter(unique);
    
console.log(someAmazingArrayOperation([9, 2, 2, 3]));
trincot
  • 317,000
  • 35
  • 244
  • 286
  • If Array.map returns an Array, when AmazingArray extends it, does that make it return an AmazingArray instead of an Array? If I chain precat after map, does it still exist? – Mnebuerquo Jul 13 '22 at 19:34
  • Yes, it is like that. This may come as a surprise, but Array methods that return a new Array, use the constructor of the object they are called on. This behaviour can be overridden with [Symbol.species](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species). – trincot Jul 13 '22 at 19:35
  • New wrinkle on this question - I'm in typescript and I get: `error TS2339: Property 'precat' does not exist on type 'any[]'.` Not sure exactly how to tell it that `any[]` is actually `AmazingArray`. – Mnebuerquo Jul 13 '22 at 19:41
  • I have no idea. Your question was on JavaScript and tagged like that. – trincot Jul 13 '22 at 19:44
  • Fair enough. Thanks for the answer. That gets me a lot closer to my solution. – Mnebuerquo Jul 13 '22 at 19:45