Array.prototype.part()

The language is missing an array method that would test whether or not there are some, but not all elements in the array that pass the test implemented by the provided function.

Currently, one of the more readable ways to do that is to use the logical AND (&&) operator to evaluate the truthy output of the Array.prototype.some() and the inversed Array.prototype.every() methods, which isn´t very ergonomic.

With this in mind, I would like to propose a new array method — Array.prototype.part(). The method tests whether there are some, but not all elements in the array that pass the test implemented by the provided function. The method returns a Boolean value. Here is an example:

([{ name: 'peach', type: 'fruit' }, { name: 'carrot', type: 'vegetable' }]).part(item => item.type === fruit); // => true
([{ name: 'peach', type: 'fruit' }, { name: 'apple', type: 'fruit' }]).part(item => item.type === fruit); // => false
([{ name: 'potato', type: 'vegetable' }, { name: 'carrot', type: 'vegetable' }]).part(item => item.type === fruit); // => false

This sounds like the .some method that was added to arrays in ES5?

Is the difference from some() that part() will fail if all elements pass the predicate? So part() == some() && !every()?

2 Likes

.some() is true when all elements match the condition. .part() will fail in this case.

1 Like

Indeed, part() will fail if all elements pass the predicate.

No, some is true when any elements match the condition; every is true when all elements match the condition.

some() is also true when all items match the condition.

My proposal, part (), will result in false if all elements match the condition.

Imagine a checkbox in a heading of a table which is checked when all rows of the table are checked. The checkbox will become unchecked if no rows are checked. It will also become indeterminate when at least one and not all rows are checked.

In this sense part() is akin to the indeterminate state of that checkbox.

2 Likes

Ah, i see. In that case, it seems like a combo of “some and not every” would indeed meet your use case.

Either way, “part” doesn’t seem like an intuitive name to me.

2 Likes

It would, but the combo is cumbersome.

Consider the statement:

items.some(item => item === 'something') && !items.every(item => item === 'something')

The need to chain the two methods with && and the need to invert every() with ! increases cognitive load and damages readability. Part() would make it short and easily comprehendible.

As for the name, “some” is already taken. “Part” to my mind corresponds to “not every” and “at least one” in this sense. I was looking for something that would reflect these two notions and couldn’t come up with anything better than “part.” I do not mind a different name as long as it is better.

This does not seem to me like it comes up very often, and can be accomplished easily with existing features as you note, and as such would be best done in that way rather than by adding a new method to the language.

3 Likes

I would disagree. every() and some() could also be accomplished with features existing at the time, say like this:

items.filter(item => item === 'something').length === items.length

instead of

items.every(item => item === 'something')

or

Boolean(items.filter(item => item === 'something').length)

instead of

items.some(item => item === 'something')

Yet every() and some() were added to the language and I see no point why part() shouldn't accompany them.

part() would only make it short and easily comprehendible if you have prior knowledge what part() does, otherwise the code becomes much more foreign, and you have to look up the documentation to understand the code. This is an understandable cost to pay if you'll be running into code like this all the time, but otherwise the cost it not worth the benefit.

From my personal experience, I've needed to use some() and every() quite often, but as far as I can remember, I've never had a need for an exclusive-or type of behavior with some(), like is being proposed here. I'm sure the proposed feature is still useful, maybe more so for other developers, but it's not on the same level as some() and every().

For this reason, if I were reading code that needed this sort of behavior, I'd rather see it just throw a utility function into the module where it was needed at with a self-explanatory name (like someButNotAll()). If I'm reading the code, and want to know what someButNotAll(items, item => item === 'something'); means, I can quickly find out by looking elsewhere in the same module, and since I already have a solid understanding of some() and every(), a utility method like this would be quickly understandable, perhaps quicker than looking up the documentation for a built-in function.


On a different note, do you know of any prior art for a feature like this in any other language or library? If so, that would help make a strong starting point - we would be able to see how they added the feature, what they chose to call it, and maybe the rationale behind it for them, and the success of the feature.

Also, how often is this sort of feature coming up for you? What are some concrete examples you're running into that would make such a feature nice to have for you? You did mention the checkbox example, are there other scenarios you've run into where you've needed a feature like this?

2 Likes

I think we should ditch this niche method, and add a more general one: count

Examples:

const some_not_all = (list, predicate) => list.count(predicate) < list.length

const at_least_3 = (list, predicate) => list.count(predicate) >= 3

const only_1 = (list, predicate) => list.count(predicate) === 1

const nice = (list, predicate) => list.count(predicate) === 69

const ultimate_answer = (list, predicate) => list.count(predicate) === 42

const no_more_than_16 = (list, predicate) => list.count(predicate) <= 0x10

// you get the idea

I propose adding another method, named countCompare which has 3 parameters (4 if we include this): predicate, comparison function, and count.

The comparison function could be any of these:
~~

const looseEq = (x, y) => x == y

const strictEq = (x, y) => x === y

const SameValue = (x, y) => Object.is(x, y)

const SameValueZero = (x, y) => Object.is(x, y) || (x === 0 && y === 0)

const lessThan = (x, y) => x < y

const evenGreater = (x, y) => x % 2 == 0 && x > y

// etc...

~~

If the comparison fn only has 1 parameter, the count parameter of countCompare is ignored, because the value to be compared is assumed to be included within the comparison fn.

This is because the comparison fn is only used to compare the running count of items that match the predicate with the count param

Actually, a better proposal is to add methods like countLessThan, countMoreThan, countEquals, etc... Much more readable, and performant