I think your take is belied by the number of posters in this very thread who report initially having the wrong interpretation.
The trailing closure version of all(satisfy:)
is grades.all { isAtLeastBPlus($0) }
which amplifies the risk of misinterpretation as a filter
-like operation IMO. This suggests it at least would need to go in the base name.
Maybe, but I think the mindset of participants in a forum looking at a name for potential sources of confusion is different from that of people (maybe even the same people) reading the method in actual use.
@Ben_Cohen Is this correct? This is surprising to me (I would expect false
) but reviewing the proposal it doesn't explicitly specify how empty sequences are handled. The equivalent code example (written in terms of negated contains
) does suggest it would return true
here though.
This is why I brought up the containsOnly(elementsEqualTo:)
and containsOnly(elementsWhere:)
as more specific alternatives, which better eliminate the ambiguity. Even so, I don't think they pull their weight.
all
was discussed in the thread that brought this proposal forward in the first place, and then community feedback saw it changed it to containsOnly
.
I still think containsOnly
is superior, as I consider it in the same family as contains
and should share a similar base name. For me, the base name preference is not driven by the fact the algorithm can incidentally be defined in terms of contains
.
I hadn't considered this before. I think that's a pretty strong argument against containsOnly
.
fwiw, the trailing closure version is the one that gives me concern about reading as an alias of filter
. If "satisfy" is visible at the call site, I don't think there's a significant risk of that mistake.
For reference, here is how Ruby handles empty sequences:
[].all? { |x| x == 1 } # => true
It is, exactly, !contains(where: !predicate)
.
It only returns false if any element does not satisfy the test (which sounds pretty solid to me).
Hmm, good point on the confusion with filtering. But Iâd think the type system helps avoid that misconception while writing code, and the differences in typical uses of booleans vs. arrays (if
vs. for
) when reading.
I think the biggest place where confusion could occur (without the compiler catching it) is when immediately converting the result into something untyped, like print(...)
or JSON serialisation.
You're right, I wasn't explicit about it in the proposal, but obliquely referenced it by describing containsOnly
as !contains(!condition)
, which does return true for empty sequences. AFAIK this is the convention in most languages (including Python, Ruby, Rust).
I'll update the proposal.
I believe the concern is that spellings like the following could be interpreted as a filter:
studentBody.all{ $0.grade >= bPlus }
If we use âallMatch
â or âallSatisfy
â (as well as âallEqual
â) then there is no confusion.
I donât really see the existence of âcontains
â and âcontains(where:)
â as having much influence on how we name this. Actually, if anything, there is an argument that we should *not* spell this with âcontainsâ:
-
If people thought about the method in question as a variant of âcontainsâ, then they would just write the double-negated version and it would be easily understood.
-
But the double-negated version is not easily understood, and people have to think harder about it to make sure they get it right.
-
Therefore, people do not generally think of the method in question as a variant of âcontainsâ.
If someone is looking for this method, I posit they are going to look for something with the word âallâ in it. After all, they are trying to find out if all elements in a sequence have some property. They are not asking nor thinking about containment.
In my opinion, the word equal should not be used because the function is available for all sequences and not only for sequences whose elements are equatable.
+1
FWIW, any/all/none
is one of the handful of extensions I blindly copy into every new bit of Swift code I play with. I will likely continue to do so if named containsOnly
, because I find all
so much better.
+1 to naming this all(equal:)
and all(satisfy:)
, or something similar.
If you want to remove the presumption of filter
, you can consider whereAll(satisfy:)
/ whereAll(equal:)
or whereAllElements(satisfy:)
/whereAllElements(equal:)
. You could sub do
for where
to make it more of a question.
It's a bit harder but not impossible to coerce the sentiment into the preferred is
-prefix, such as isUniformly(equalTo:)
/isUniformly(satisfiedBy:)
. Using is
clarifies the Boolean nature of the call and adheres to the API guideline:
Uses of Boolean methods and properties should read as assertions about the receiver when the use is nonmutating, e.g. x.isEmpty, line1.intersects(line2).
I agree that "only" can be misread as "only one".
If "all" looks like filtering, maybe it needs a boolean prefix:
grades.containsAll(4)
grades.containsAll(where: isAtLeastBPlus)
grades.isAll(4)
grades.isAll(where: isAtLeastBPlus)
Or you could try "exclusively" or "strictly" instead of "only":
grades.containsExclusively(4)
grades.containsExclusively(where: isAtLeastBPlus)
grades.isExclusively(4)
grades.isExclusively(where: isAtLeastBPlus)
I wasn't going to say it, but I was thinking the same thing.
Agreed. Boolean contexts are almost overwhelmingly obvious from the use site, so the risk of this confusion is very low. containsOnly
doesn't have that advantage.
To me, using named predicate with trailing syntax like this is a code smell that linters should care about. IMHO, trailing syntax is for inline closures, where this confusion does not arise.
Generally, the possibility to omit the argument labels, which are there to increase the clarity at use site, when using trailing closure syntax isnât a valid reason for moving them into core method names. Consequently applied it leads back to the Obj-C/Cocoa naming, not the Swift naming guidelines.
Also I donât understand the concerns about potential mis-interpretations as filtering, in a strongly typed language. (I would grant them if compiler couldnât smack you on the head for confusing Bool
with [T]
.)
It seems to me that when way back when the contains
was added, the name made sense in isolation. But now that we consider adding its dual, we have exposed that name as inadequate. What exactly is the cost trade-off for contorting ourselves with containsOnly
and expanding the Swift Galapagos, versus embracing established programming terms of art and renaming contains
to any
as we introduce all
?
What exactly is the prohibitive(?!?) cost of renaming a single method by simply deprecating the old name, making it forward to the implementation with new name, and providing automated migration fixit? Why do we have to live with all the past mistakes forever?!
I would prefer the plain any
/all
/none
, but if someone feels like a prefix is required then would areAny
/areAll
/areNone
make more sense and read better for plural variable names than the is
variants, which seem less clear as to whether they apply to the Sequence
or its elements?
In the pitch thread, Brent pointed out that many of the predicate functions passed in as arguments will start with âisâ, such as âisEmptyâ or âisWhitespaceâ or âisPassingGradeâ. If the base name of the method began with âisâ or âareâ then these arguments would make call-sites read poorly.
I think weâre on the right track with either allMatch(_:)
or allSatisfy(_:)
for the version taking a predicate, and allEqual(_:)
taking an equatable element.
Sure, that's one downside. is-
predicates read fine with any
/all
/none
though, which would be my preference and avoids having separate names. I don't find the extra words that clarifying (they rely on the somewhat subtle difference between match and matching) and I think any
/all
/none
are common enough in other languages to qualify for a precedent/terms-of-art exception to the âavoiding ambiguityâ rule. I believe the boolean nature will generally be obvious from context.