This proposal prompted a lot of very interesting discussion. Many members of the community favor adding these methods, but others are concerned about opening the door to a large number of trivial additions. Adding many trivial methods just clutters up the API without significantly improving the power of the library. This sort of clutter has a real impact on usability because all of the trivial methods get equal prominence in things like documentation and code-completion results, making it harder to find what you actually want. The Core Team agrees that it's important to set clear standards here.
Almost all library features can be composed from existing features with enough effort. To be considered for addition to the library, a proposed feature must satisfy two conditions: it must provide functionality that is useful to a substantial population of Swift programmers, and it must provide substantial advantages over the alternative ways of accomplishing that functionality. Potential advantages include:
- It may be complex, challenging, or error-prone for users to implement themselves.
- It may have substantial performance advantages over a user implementation, either because it has access to library internals or just because the library implementation will likely be more carefully tuned.
- It may be substantially easier to work with because it composes better with other language or library features.
- It may be substantially more "fluent": that is, more natural to discover, use, and read in code. The implementation may involve composing primitives in a subtle or tricky way, or the primitives may be unfamiliar to many programmers. This is a more subjective criterion than the others, and people may reasonably differ about how to apply it.
For example, consider adding an method to rotate the elements of an array. This operation can be performed using a relatively simple composition of slicing and concatenation:
array = array[amount...] + array[..<amount]
However, a method to rotate an array in-place would still be welcome: it would have substantial performance advantages, it would avoid several potential off-by-one errors, and it would be significantly easier for a reader to recognize as a rotation.
In contrast, a property like isNotEmpty
offers no substantial advantages over !isEmpty
, and adding it would open the door to adding trivial negations of essentially every predicate.
Applying these principles to SE-0225, the Core Team feels that isMultiple(of:)
has substantial advantages over the alternatives:
- It is substantially more fluent than its standard implementation of
value % divisor == 0
. Divisibility testing is the operation people actually want; the%
operator is just the way they have to achieve it.%
is rarely used outside of this idiom, and many programmers have no other reason to know it. It is not a familiar operator for new programmers especially: they will usually be comfortable with remainders in the context of division, but they aren't used to thinking about computing a remainder as a separate operation, and they certainly don't recognize%
as a symbol for it. Even experienced programmers often mentally "pattern-match" this sort of expression to recognize it as a divisibility test instead of thinking through the arithmetic of it. - Encouraging the use of
isMultiple(of:)
(and!isMultiple(of:)
) serves to counter bugs around negative remainders. - It has some potential for better performance, especially when applied to large integers. In this case, this impact would probably not be a sufficient justification on its own.
Given the addition of isMultiple(of:)
, the Core Team feels that isEven
and isOdd
offer no substantial advantages over isMultiple(of: 2)
. The only arguable advantage from the list above would be performance, but the compiler should be able to reliably optimize isMultiple(of: 2)
to a simple bit-test in practice.
Therefore, the proposal is accepted with modifications. isMultiple(of:)
is accepted but isEven
and isOdd
are rejected.