I don't think it is really inconsistent. Yes, they might be a little confusing as JavaScript arrays do all the things for which other languages have separate structures (list, queue, stack, …), but their definition is quite consistent across languages. You can easily group them in those categories you already described:
- list methods:
push/unshift return the length after adding elements
pop/shift return the requested element
- you could define additional methods for getting first and last element, but they're seldom needed
splice is the all-purpose-tool for removing/replacing/inserting items in the middle of the list - it returns an array of the removed elements.
sort and reverse are the two standard in-place reordering methods.
All the other methods do not modify the original array:
slice to get subarrays by position, filter to get them by condition and concat to combine with others create and return new arrays
forEach just iterates the array and returns nothing
every/some test the items for a condition, indexOf and lastIndexOf search for items (by equality) - both return their results
reduce/reduceRight reduce the array items to a single value and return that. Special cases are:
map reduces to a new array - it is like forEach but returning the results
join and toString reduce to a string
These methods are enough for the most of our needs. We can do quite everything with them, and I don't know any libraries that add similar, but internally or result-wise different methods to them. Most data-handling libs (like Underscore) only make them cross-browser-safe (es5-shim) and provide additional utility methods.
What I would like is to always mutate the array and always return the same array, so I can have some kind of consistency and also be able to chain.
I'd say the JavaScript consistency is to alway return a new array when elements or length are modified. I guess this is because objects are reference values, and changing them would too often cause side effects in other scopes that reference the same array.
Chaining is still possible with that, you can use slice, concat, sort, reverse, filter and map together to create a new array in only one step. If you want to "modify" the array only, you can just reassign it to the array variable:
A = A.slice(0,1).reverse().concat(['a','b']);
Mutation methods have only one advantage to me: they are faster because they might be more memory-efficient (depends on the implementation and its garbage collection, of course). So lets implement some methods for those. As Array subclassing is neither possible nor useful, I will define them on the native prototype:
var ap = Array.prototype;
// the simple ones:
ap.each = function(){ ap.forEach.apply(this, arguments); return this; };
ap.prepend = function() { ap.unshift.apply(this, arguments); return this; };
ap.append = function() { ap.push.apply(this, arguments; return this; };
ap.reversed = function() { return ap.reverse.call(ap.slice.call(this)); };
ap.sorted = function() { return ap.sort.apply(ap.slice.call(this), arguments); };
// more complex:
ap.shorten = function(start, end) { // in-place slice
if (Object(this) !== this) throw new TypeError();
var len = this.length >>> 0;
start = start >>> 0; // actually should do isFinite, then floor towards 0
end = typeof end === 'undefined' ? len : end >>> 0; // again
start = start < 0 ? Math.max(len + start, 0) : Math.min(start, len);
end = end < 0 ? Math.max(len + end, 0) : Math.min(end, len);
ap.splice.call(this, end, len);
ap.splice.call(this, 0, start);
return this;
};
ap.restrict = function(fun) { // in-place filter
// while applying fun the array stays unmodified
var res = ap.filter.apply(this, arguments);
res.unshift(0, this.length >>> 0);
ap.splice.apply(this, res);
return this;
};
ap.transform = function(fun) { // in-place map
if (Object(this) !== this || typeof fun !== 'function') throw new TypeError();
var len = this.length >>> 0,
thisArg = arguments[1];
for (var i=0; i<len; i++)
if (i in this)
this[i] = fun.call(thisArg, this[i], i, this)
return this;
};
// possibly more
Now you could do
A.shorten(0, 1).reverse().append('a', 'b');
A.slice(0,1).reverse().forEach(function(){});- works as expected. I'm not sure what your point with this example is. You state that you would like to be able to chain, and then you give an example with chaining using the existing methods, that works. Please provide examples that don't work with the existing methods, so that I can get a sense of what you would be able to write.slicedoesn’t mutate the array like I want - it returns a new array. I can give more examples if it helps.pop(), and.shift()? They remove one element from the array, and return that element. Do you want them to return the array instead?