Thank you very much @Ben_Cohen for taking the time to research and write that. I really appreciate the feedback.
I’d like to go through the criteria and try to make a stronger case in a few key areas.
Commonality: One issue I’ve had with finding supporting examples is that % 2 == 0
isn’t very search engine friendly (if anyone has a trick, please let me know!). For example, Github searching the apple/swift repo yields zero results, but doing a local text search of the cloned repo yields 63 results. Most results are in test
and benchmark
but I would still consider this to be “real-world” code.
A few other examples in apple/swift include:
// Codable.swift.gyb
guard count % 2 == 0 else { throw DecodingError.dataCorrupted ... }
// Reverse.swift: documentation for public let base: Base.Index
// guard let i = reversedNumbers.firstIndex(where: { $0 % 2 == 0 })
// Deserialization.cpp
assert(e % 2 == 0 && "malformed default witness table");
There are also a few for % 2 == 1
which are potentially bugs for negative integers.
// RangeReplaceableCollection.swift: documentation for removeAll(where:)
// numbers.removeAll(where: { $0 % 2 == 1 })
// AnyHashable.swift.gyb.
if (lhs % 2 == 1 || rhs % 2 == 1) && (lhs / 8 == rhs / 8) {
& 1 == 0
is also used in a few apple/swift tests and Bool.swift:
// Bool.swift: random<T: RandomNumberGenerator>
return (generator.next() >> 17) & 1 == 0
Other examples based on searches of my local repositories:
Some really real-world examples:
I’d also like to push back on discounting example/sample code usage. This type of code is often written by a more experienced developer for a much larger but less experienced audience. A single blog post, playground, book, or tweet-sized snippet can be read thousands of times, which I think legitimately qualifies as real-world usage, even if it doesn’t end up in a production software product.
Readability: Criteria met.
Not trivially composable: This functionality is trivial to implement, once you’ve dug around in the Swift integer protocol hierarchy to figure out that BinaryInteger
is the appropriate protocol to extend.
There is one case where they aren’t trivially composable: the sample/education usage. In this context, it’s usually not appropriate for an author to introduce this functionality (unless they are teaching extensions!) in order to avoid distracting from the main task at hand (e.g. filter, map, etc). It may also be the same situation for authoring test code: it'd be used if it were there but it's not worth the overhead of defining it manually.
The “trivial inverse” issue isn’t something I have a great answer for, other than there is precedence for this in standard libraries of other languages (Ruby, Haskell, Clojure, and according to RosettaCode: Julia, Racket, Scheme, Smalltalk, Common Lisp).
Sufficiently general: isDivisible(by:)
is certainly more general than isEven/isOdd
, and %
is more general than isDivisible(by:)
.
A very preliminary survey seems to indicate that % 2 == 0
is used significantly more often than other divisors.
Apple/swift:
-
% [0-9]+ == 0
: 105
-
% 2 == 0
uses: 63 (60%)
My workspace of projects and cloned repos (not including apple/swift)
-
% [0-9]+ == 0
uses: 240
-
% 2 == 0
uses: 192 (80%)
Perhaps this somewhat justifies the specificity. Also, even and odd numbers have had shorthand labels for thousands of years.
Discoverability: .isEven/.isOdd
are certainly more discoverable than % == 0
and % != 0
. I completely agree that learning about %
was and still may be a necessity, just as learning c-style for-loops probably was for many of us earlier on in our developer lives. The great thing about Swift is that it opens up new and better ways of expressing an idea, such as a method on an integer type, that weren't possible before.
Performance traps: N/A
Discourages/encourages misuse: Discourages potential misuse of %
in the case of negative dividends (see next) when determining oddness.
Correctness traps: % 2 == 1
for negative dividends is a potential source of bugs and confusion. -3 % 2
evaluates to -1
in Swift; the same expression evaluates to 1
in Ruby and Python, for example, due to differences in the semantics of %
. There is CERT C coding standard warning about this style of problem due to implementation-defined behaviour in earlier C versions. Modern polyglot developers still need to worry about the same problem though!
Can be implemented more efficiently in the std lib: Not really.
I know this is going to be a tough sell, especially to an audience that is likely quite experienced, but I think that uses for isEven/isOdd
are frequent enough and offer a big enough improvement to justify the (small) weight they add to the standard library.