Add an all algorithm to Sequence

Plain isEach works well in the form isEach(42).

Clearly, the spelling is clunky without an intervening label for the predicate. isEach(matchedBy: isOdd) or something along those lines would do nicely.

One should note, incidentally, that this particular shortcoming you point out is also present in containsOnly(isOdd), which is wildly ungrammatical.

It is !contains. There is a reason I made no comment on that. See below.

I think the discoverability argument is a red herring.

For one: contains is a very succinct name that, in Swift, is used only (pun intended) in the context of "contains at least one of." Reusing the term contains in a different way (in the context of containsOnly) dilutes the existing meaning. It would be one thing if the method we have today were instead named containsAny: then, I would agree that a highly suitable name for this proposed method is containsOnly. But that's not the case.

For another: Either there are so many methods on Sequence that there's a discoverability issue, in which case we should not be adding this convenience at all--after all, it's not hard to use contains to accomplish the same task--or, alternatively, there are not too many methods on Sequence that there's a discoverability issue we need to worry about.

As I noted above in my reply to Brent, the same problem applies to containsOnly and all. Consider the case of all(isOdd). Your English teacher would have a heart attack. ["But," you might say, "don't you know of the Christmas carol where they sing, 'All is calm'?" For an explanation for why your English teacher would still have a heart attack, see this concise explainer.]

Don't mistaken redundancy with completeness. The Swift standard library is deliberately small, and it does not provide negated versions of methods.

The "toggle()" proposal was not about adding a negated version of an existing method, and the conversation was (inappropriately) diverted to discuss something unrelated that, frankly, will not happen. This pitch does not propose, and has never proposed, to add a method that is the negated version of contains. Please don't divert this conversation also to discuss the same thing.

A long time ago, if I recall, someone proposed on this list that all related methods should start with the same prefix. The core team made it clear that they do not subscribe to this point of view.

isEach is wildly ungrammatical in any case.

...how so?

Okay, but just because it's not always the right answer doesn't mean it's never the right answer. You are not showing that it's not the right answer, just appealing to some vague precedent.

The use of is implies there is an attribute to the subject of is. What is the attribute? What is the adverb modify? Or is "each" used as a determiner? What does it determine?

You came up with this by substituting is for for in forEach. for is a conjunction, which is most definitely is not. The result is a syntactical conundrum.

A sequence is a container (of sorts) for elements. It does not be the elements it contains.

3 Likes

The Swift API design guidelines tell us to "strive for fluent usage." By fluent, it's clarified that we are to make "use sites form grammatical English phrases" (emphasis mine). Some examples given are:

x.insert(y, at: z)          // “x, insert y at z”
x.subViews(havingColor: y)  // “x's subviews having color y”

For Sequence, the word "element" is generally elided:

x.first           // "x's first [element]"
x.dropFirst()     // "x, drop first [element]"
x.forEach { ... } // "x, for each [element]..."

Therefore:

x.isEach(42)  // "x, is each [element] 42?"

Personally, I think that each sounds very individual which doesn't fit well with the fact that isEach would be combining all the elements into one final value (compared to forEach which runs something on each element but doesn't combine the results), and would prefer words like every or all for the naming of a method that combines information about all the elements in a collection. Either way, I prefer containsOnly to any of the previous.

Also, it feels weird to have 3 of the 4 methods, where's the containsOtherThan? Personally, I don't feel the need for containsNone (which can be written as !contains or contains().inverted or toggled or whatever) but if you're going to have that, you should have its counterpart too.

My vote goes to onlyContains(_:) and onlyContains(where:). I think they most closely resemble plain English and they make the connection with contains very obvious. containsOnly is a close second.

I find mySequence.isEach(42) wildly confusing!

With mySequence.containsOnly(42) it really does read like English. Does my sequence contain only 42s?

With isEach, I am reading it as "mySequence is"... ok, my sequence is what? It is an "Each 42". This makes no sense because mySequence is a single object and "each" only has meaning for groups of things. A sequence may contain multiple things, but it is not itself multiple things.

Adding "element" does help a bit, but I don't think you can get away without explicitly saying it like you can with the others.

I was never in love with forEach, but it does at least (barely) work because of the "for".

Also, why do you feel like the method should be grouped with forEach instead of contains? It would seem to me that contains is much closer in spirit to what this method does...

I like this proposal – this will be a nice and needed addition.

I'm leaning towards the containsOnly naming:

extension Sequence {
    func containsOnly(where predicate: (Element) throws -> Bool) rethrows -> Bool
}

extension Sequence where Element: Equatable {
    func containsOnly(_ element: Element) -> Bool
}
2 Likes

+1 for containsOnly.

(can we +1 if there's not an obvious post to heart?)

1 Like

idk why x.isEach(42) sounds okay to you but it most definitely is weird asf just because the phrase “is each” never occurs in English. it’s always either “are each” or “each is”. Each implies a plural self, while is requires a singular self. this is self-contradictory.

5 Likes

+1 for containsOnly.

Just wanted to reiterate my support for containsOnly now that this has picked up again. It reads very naturally, and it aligns well with the existing contains, since it can easily be seen as a variation of it.

It also satisfies this API design guideline, while many of the other suggestions do not: Uses of Boolean methods and properties should read as assertions about the receiver.

The litmus test I use for determining whether a boolean API reads naturally (and what I think is the implied motivation of the above rule) is pairing it with an if, i.e. "if sequence contains only 5" vs. "if sequence is each 5". The former is valid, natural English while the latter is not.

5 Likes

+1 for containsOnly(_) and containsOnly(where:).

I doubt that anyone wants to touch contains(_) or contains(where:) at this point. Given that those names are fixed, the "all" versions really ought to conform to them in their naming.

I agree with TellowKrinkle that the parallelism between isEach() and forEach() is kind of actively misleading. There is no indication in the name isEach() that the basic operation is a form of AND. It suggests something that would produce a series of Booleans.

1 Like

Would love to drive this forward and improving the current / creating a new proposal. Is that something we can get through in the Swift 5 timeframe?

Guys, by any chance..., can we go with contains(only:) instead of containsOnly? It's almost Swift 5 we're talking about after all :)

it may cause problems

a.contains(1)
a.contains(where: isOdd)
a.contains { $0 % 2 == 0 }

a.contains(only: 1)
a.contains(only: isOdd)
a.contains { $0 % 2 == 0 }  // ????
4 Likes

Yes, it will, excuse my inadvertence. containsOnly is good, but too old-fashioned and long. IMO such semantics should be discouraged further on. In particular, using names by concatenating a single word with multiple others. Instead, synonyms or tucking the secondary semantic part (here Only, None) into the argument's label when possible. I believe all , as @Ben_Cohen originally suggested, should be given a chance. But surely without omitting the argument label. Should be all(equal: something), not all(something).

1 Like

I find myself leaning more and more toward “all”. As in:

a.all(1)
a.all(isOdd)
a.all{ $0 % 2 == 0 }
2 Likes