SE-0225: Adding isEven, isOdd, isMultiple to BinaryInteger

  • What is your evaluation of the proposal?
    +1
  • Is the problem being addressed significant enough to warrant a change to Swift?
    Yes - it is tricky to code correctly yourself.
  • Does this proposal fit well with the feel and direction of Swift?
    Yes
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
    Similar, I have used divisible in other languages, e.g. Mathematica, but isMultiple(of:) is an improvement.
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
    Followed the thread.

I appreciate the feedback from you and everyone else taking the time to comment on this!

I realize that the proposed changes are right on the edge of being worthy additions to the standard library and I'm glad there is pushback against it. Something this close to the edge should not be rubber-stamped into the standard library without thoughtful consideration to the benefits and costs. I've tried to do that with this proposal but it's clear that it is far from perfect.

I think including only one of isEven/isOdd would be highly unusual when compared to other languages. I'm not aware of any other language that includes only one.

I disagree with this. Of course Swift is free to evolve in ways that diverge from other languages and their standard libraries, but failing to consider their APIs would be a big net loss, imo.

isMultiple was not added to the proposal without consideration. Arguments for including it are very similar to isEven/isOdd: it appears frequently in various contexts, readability improvements, and somewhat surprising % semantics. Additionally, it has potentially positive performance implications for complex integer types, and the implementation is not quite as trivial as you might expect.

Some context: The very initial motivation for my pitch for adding isEven/isOdd was while reading a book for a third party Swift library. This book repeatedly used value % 2 == 0 and value % 2 == 1 to demonstrate the use of their own operators and I realized I had seen this before in many other educational contexts. Then it struck me that it was somewhat strange choice for a learning environment since it relies on operator precendence rules (multiplication group vs comparison group) which to a student or inexperienced user may be non-obvious and is likely distracting from the core lesson.

Re-reading that line in the proposal, it's clear that the context was lost but it remains true that two operators are currently required to determine if an integer is even, odd, or a multiple of another number. Discussing whether or not the precedence rules are obvious is not something I'd like to spend a lot of effort on - and for that reason I do regret including that phrasing in the proposal.

I appreciate your response and truly value your feedback, particularly regarding the portions of the discoverability and trivial composability sections you've highlighted.

I may be mistaken, but I believe that any precendents would be set in the decision rationales and not necessarily the proposals themselves. It seems like any rationale to accept the proposed changes could explicitly strike any "troublesome" aspects, but I am open to revisions if necessary.

Any new non-operator member :) I bring up autocomplete because it would be a concrete enhancement over the % operator discoverability for the even/odd/multiple use case, which is not suggested as part of autocomplete on integers.

3 Likes

I disagree. While you can certainly argue that a modulus is a "basic" idea, it certainly isn't common. The only time I ever use it is for this specific scenario, i.e. to immediately test it against zero for the purpose of parsing every third element of an array or whatnot. So rather than being common it's actually a pretty rare thing that is pretty ill suited to its most common use. To resort to a modulus just to count by three feels very much like a kludge, and as the proposal states the millions of times new coders have searched "how to count by X" expecting to find an elegant solution is a testament to that. We can do better.

2 Likes

I am mildly -1 on this proposal, even though I am generally positive towards including new functionality into the stdlib - this just doesn't cross the threshold for inclusion for me.

Rationale: I think that many coders would implement this themselves in terms of % rather than even learn that there is builtin functionality for this, so these would not be widely used. People who need this but who do not know about % would quickly find answers to how to do this on stack overflow, so continuing to omit this would not cause harm.

Other languages don't have this, and everything seems to continue working fine. I don't think it is important to add solutions to every possible thing commonly asked on stack overflow, and don't see how this is a particular priority.

I have no opinion on this either way.

The only time I've encountered something like this is in big integer libraries, which sometimes have things like this for performance reasons. That rationale (performance) there doesn't apply here. I haven't heard of C or C++ programmers introducing widely used free functions for this, so I don't think there is a great urgency to do this.

I read the proposal but did not follow the epic pitch threads.

-Chris

7 Likes

The will be a proposal for mod in the near future:

I'm not sure wether this would make SE-0225 superfluous, but imho mod is definitely more important and makes it trivial to implement isOdd correctly.

2 Likes

.count vs. .isEmpty is not sugar. The properties have different performance requirements. They're the same for Array, but not for collections in general.

3 Likes
  • +1
  • yes
  • yes
  • I've tried designing math libraries, so I can see how these functions help optimize.
  • quick reading

Since the % operator has different semantics in different languages (see eg Swift's vs Python's % when lhs is negative) and the common pitfall of testing for oddness by writing x % 2 == 1 rather than x % 2 != 0 is common enough to appear in several learning-Swift-resources as well as in Swift's std lib docs, I think it's clear that people does not quickly understand the semantics of % (in language L) correctly, so implementing this functionality correctly is not trivial, and neither is quickly understanding the implications of eg x % 2 == 1. Will it be true for x = -3?

That depends on what language we're using, In Python:
(-3 % 2 == 1) == True.
But in Swift:
(-3 % 2 == 1) == false.

And stack overflow is full of x % 2 == 1, for example here.

Thus I'm mildly in favor of adding the proposed functions.

(I would be more in favor of actually removing %, replacing it with a more complete/proper set of remainder functions, accompanying truncated, floored and euclidean division. Though I realize removing % will probably never happen.)

8 Likes

Opposite for isEven and isOdd.

I've confused by the statement that precedence of the operators % and == not obvious, because this is two totally different operators, and I like managing of the precedence in Swift, imo, it easier then in other languages.

Saving few characters of the space in the modern world where we all have not so small screens even in laptops looks a bit outdated. Yes, most still use some limitation lines (me too), but it is not about saving few characters anymore.

No.

Hard to say, it seems to be more friendly for new users, but I'm not sure if it used frequently enough to be added into language.

No.

I've read about half of the discussion in the original pitch and proposal.

What is your evaluation of the proposal?

+1

Is the problem being addressed significant enough to warrant a change to Swift?

Yes. I was convinced only by the example code in the Motivation section.

Does this proposal fit well with the feel and direction of Swift?

Yes. It reminds me of OptionSet : SetAlgebra, which uses methods and properties (rather than bitwise operators) to improve readability.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

A quick reading of the proposal.

1 Like

Strong +1 for isMultiple(of:), weaker +1 for isEven + isOdd, -1 for only isEven or only isOdd.

As opposed to isNotEmpty, there's really no clear reason why isOdd should be subordinate to isEven (or the other way around, but nobody seems to be suggesting that). I really feel that the language would feel very awkward including only one of the two cases. I also think that this would be weird for discoverability purposes, people who see isEven would also expect isOdd.

Arithmetic modulo 2 is very common for a number of reasons, not the least of which is that computers work with binary numbers. More in general, modular arithmetic is generally useful for a whole number of reasons (dealing with clock time, weekdays, cyclic lists, etc.). I think these are abstractions worth having, not necessarily because it would be so hard to implement them (while there are some minor pitfalls, I guess those would be easily discovered), but because the readability is so much enhanced. I believe in domain driven design, and in a lot of cases isEven or isMultiple(of: n) is the right level of abstraction, x % 2 == 0 is not.

In addition, I fail to see any substantial negative about this proposal, as it is strictly additive, and I don't really understand the strong opposition some people have about these variables/methods. Would it really make Swift worse?

This seems to be heavily debated, with some people feeling that the stdlib should stay small and focused. I personally have little skin in this game; I used to write Ruby, and while there were a couple of things that annoyed me about it, the fact that the stdlib has a big surface area never was one of them.

That said, I would also be fine with a semi-official, or just very well-known, Swift library that implements common things like these so the stdlib can stay more focused; maybe even something that is included by default in the Swift runtime but needs to be imported—I think that would be a discussion worth having.

Ruby has even? / odd?, which work the same as isEven / isOdd proposed here. To my knowledge it doesn't have isMultiple(of:).

I read the proposal and contributed to the previous pitch thread.


Two additional notes:

  1. I agree that a mathematically correct division with remainder operation (as either one function returning a tuple or two functions) would make a lot of sense.
  2. I think the proposal document could be improved in some places, e.g. the section about isMultiple(of:) vs. isDivisible(by:) could be clearer.
9 Likes

+1.

On supplementing %

The a % b == 0 idiom is familiar to programmers. That’s not necessarily a good thing: it clouds our judgement. Familiarity bias is (IMO) the primary factor in our opinions about language features; we’re simply not as objective as we like to imagine.

Does familiarity cover up a problem here? I say yes: % b = 0 isn’t as fluent or as self-evident to newer programmers as .isMultiple(of: b). Then there’s also the issue of trapping on zero, an easily overlooked source of bugs. And the pitfall of using % 2 == 1 instead of % 2 != 0 or & 1 == 1 to test for oddness is a mistake I’m sure I’ve made at some point.

Mathematicians are a few centuries ahead of us on the question of whether divisibility needs its own special notation, and they long ago decided that the answer is definitively yes: number theorists write a | b, even though they already have the notation a ≣ 0 (mod b). The rationale for that (readability + the pernicious b = 0 case) was the same in math as it is in programming.

Swift has not been shy about departing from longstanding idioms in the name of improving readability and preventing errors — even in ways unprecedented in related languages. Consider the C-style for loop. The language shouldn’t be shy here either.

On the threshold for inclusion

I'm a fan of ergonomic conveniences like this for readability reasons alone; when they avoid bugs (trap on zero; % 2 == 1 fails to handle odd negative numbers), I’m doubly a fan. But there is still a high standard for adding methods like this to a commonly used type in the stdlib:

  • Would the method actually be used in the wild?
  • To a reader not familiar with the method, is its meaning self-evident upon reading in context — or at least not prone to misinterpretation?
  • Does the name potentially clash with other possible meanings we might also want to add to the type?

Would the method actually be used in the wild?

Well, I certainly would have had occasion in the past use all three of the proposed methods.

I might be lulled into thinking they’re not necessary if I’m working on widget-y apps and web sites for a while … but come the next game, graphics app, or number-crunching task, it all comes rushing back.

To a reader not familiar with the method, is its meaning self-evident upon reading in context — or at least not prone to misinterpretation?

Clearly yes. We all know the words “even,” “odd,” and “multiple.”

Here is my minor quibble: in mathematics, for the assertion “there exists some integer k such that a=kb,” the most widely used terminology is “a is divisible by b.” The word “divisible” means exactly what the proposal says (including always being true when a=0). That’s the term people use in math class from roughly middle school algebra on. It bothers me slightly to be abandoning the preferred term of art for a well-known concept, contrary to the API guidelines.

However, “multiple” is also widely understood; if it’s less prone to misinterpretation, so be it!

Does the name potentially clash with other possible meanings we might also want to add to the type?

It is hard for me to imagine methods with any of these three names have a different meaning that is also correct.

Does this matter enough to bother with?

Swift certainly has bigger fish to fry. The gaps in generic types and reflection are far larger problems.

Why bother with this battle, then? Swift is a language whose design can accommodate little stdlib niceties like these. Having worked in languages both many ergonomic conveniences and few, I’ve come to land squarely in favor of them when they are well designed.

These proposed methods are simple to understand and easy to implement. They prevent errors. Their greatest cost for the language and the community is this discussion thread. If we get too persnickety about including simple, nice things in Swift, then 5 or 10 years down the road, working with the resulting language will be neither simple nor nice.

21 Likes

I realize that this is a small proposal, and definitely subject to Parkinson's Law of Triviality (cf. the term "bikeshedding"). But I'm actually in favor of adding isEven, purely because I think having a bit of sugar in the standard library is a good thing. It's the same rationale as having contains(where:) and allSatisfy(where:) even though they're "just trivial wrappers" around first(where:) or firstIndex(where:). I don't think code needs to read exactly like an English sentence, and I don't think value % 2 == 0 is that difficult to read, but value.isEven is even easier.

That said, I know that this is right on the borderline, and while it falls on one side for me and what I want for the Swift standard library it's clearly on the other for others.

I'm ultimately in favor of isOdd as well. Evenness does seem to be slightly more fundamental, since it's a special case of divisibility, but it doesn't seem to be so much more fundamental as to make isOdd automatically spurious, and I suspect it'd be seen as an odd omission in the StackOverflow posts of the world if we left it out. There's no negation in the name itself, just the definition of the operation.

(For cross-language comparison, Ruby has both isEven and isOdd under the names even? and odd?. I don't know of any other standard libraries that have it built in.)

I'm less interested in isMultiple(of:) myself, since

  • it probably comes up much less often in the first place (though I'm not sure where this impression comes from)
  • even then it's probably not being used on negative numbers
  • it doesn't "feel lighter" than % n == 0, or at least not as much, so if we take isMultiple(of:) but not isEven I think people will still hit the == 1 pitfall
  • at the same time, the == 1 pitfall only applies to checking for odd numbers, not any other divisor
  • there's no optimization opportunity (while isEven only checks the low word of a BigInteger)

But I think the name works well if we do take it; props to Dany for coming up with it.

6 Likes

What is your evaluation of the proposal?
-1 on all 3

Is the problem being addressed significant enough to warrant a change to Swift?
No

Does this proposal fit well with the feel and direction of Swift?
No don't see the point of so much effort for so something so simple.

If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
Yes and similarly doubtful this would ever have garnered so much debate.

How much effort did you put into your review? A glance, a quick reading, or an in-depth study?
Read the proposal and followed part of the debate.

1 Like

does anyone actually have any evidence that students get confused by basic operator precedence or are we just theorizing here?

1 Like

If the operand is small, it's still possible to decompose (x0 + x1*(1 << n) + x2*(1 << 2*n) + ...) mod y into x0 mod y + x1 mod y * (1 << n) mod y + .... Many small divisions can be faster than one large division, and in many cases, the series of (1 << n) mod y values is constant (for instance, 2**(32*n) mod 3 is 1 for all integer n) or periodic. I suppose none of this is specific to the "is multiple" operation over the more general modulo operation, though.

Exactly.

Right. In the common case for bignums (testing large for divisibility by small), we can do much better than trial division either as Joe describes or via Montgomery multiplication. This reduces the complexity considerably, and eliminates the need for auxiliary storage space.

2 Likes

+1 to all three.

These are all pleasantly discoverable and will ease/defer the challenge of explaining the particulars of modulo to newcomers.

I followed the pitch and I've read the proposal.

EDIT: isMultiple(of:) is useful in music, game implementations, and possibly graphical contexts

7 Likes

There are programmers out there who are not aware (never knew, or knew once and forgot) that the % operator has sharp edges when negative numbers are involved, and that different languages have different results from this operator. This seems like a dangerous situation.

There are programmers out there (me included) who know that % has sharp edges and that different languages have different results, but who don't use the operator enough to remember the behavior in any specific language. These programmers will consult the documentation, which is a safe enough strategy but it can be a burden to find the appropriate documentation.

isMultiple(of:) states what it does and what its result is, unambiguously. There are no quirks to remember, and there's no need to consult documentation.

Therefore, I say, it's very much worth having in the standard library for its semantic clarity, regardless of its functional triviality. That clarity isn't just cosmetic, it solves an actual conceptual problem that leads to actual, subtle bugs. So that's a big +1 from me.

I don't care much about isEven or isOdd. I'd prefer to guide such use cases towards isMultiple(of:2), and not overly inflate the significance of evenness and oddness in computer science, but … whatever.

12 Likes
Terms of Service

Privacy Policy

Cookie Policy