Development

On the Origin of isMultiple

Rob MacEachern • Nov 20th, 2018

How isMultiple became part of Swift, and what I learned along the way.

We’ve been using Swift at Steamclock for almost four years, and in that time it’s come a very long way. While Apple has of course done a momentous amount of work on the language in that time, a lot of Swift’s maturation can be attributed to the work of the community, by way of Swift Evolution.

Ideas move through Swift Evolution in three steps: a pitch, a proposal, and a review. There have been hundreds of reviewed proposals so far, ranging from the addition of the Random API to the removal of C-style for loops, and get as deep as making improvements to the floating point and integer protocols.

These proposals and many more are discussed on the Swift Evolution forums every day, which is where I learned about Chris Eidhof’s proposal to add toggle() to Bool. toggle adds a convenient way to to negate the value of a boolean.

This is particularly useful when a boolean is nested deep in an object graph, like so:

// Before .toggle()
someObject.someProperty.someBoolProperty = !someObject.someProperty.someBoolProperty

// After .toggle()
someObject.someProperty.someBoolProperty.toggle()

Chris’ proposal was easy to understand, the implementation was only a few lines of code, and the changes positively impacted the ergonomics of the language. This proposal showed that improvements don’t necessarily require intimate knowledge of the Swift compiler, or obscure data structures.

At the same time, it exposed some divisions in the Swift Evolution community. Some users argued that such trivial additions weren’t worthy of inclusion in the Swift Standard Library. Accepting toggle, in their eyes, would inspire a horde of trivial additions that would pollute the API surface of the standard library.

Despite these objections, the toggle() proposal was accepted on March 7, 2018, along with a refined set of proposal criteria and an explicit call for “further proposals to fill in gaps like this in the standard library.”

Which was great news, since toggle had planted a seed in my mind.

The Lightbulb

Around that time, I had been reading a book about Swift. Although its code samples were generally quite readable, there were a number of examples that included tests for even and odd numbers using the “remainder” operator, %.

.filter { integer in
    integer % 2 == 0
}

.skipWhile { integer in
    integer % 2 != 0
}

.takeWhile { index, integer in
    integer % 2 == 0 && index < 3
}

Many programmers will be familiar with integer % 2 == 0 as a check for a zero remainder after division by two – implying the number is even – or a non-zero remaninder indicating the number is odd. It’s not rocket science (although admittedly it may be a very tiny simple part of rocket science).

After seeing after seeing integer % 2 == 0 enough times, however, it started to look very strange to me. The strangeness may have been partly due to semantic satiation, but despite having used this pattern dozens of times in various projects – and despite it being standard practice for testing integers for “evenness” – it looked unnecessarily obtuse.

Meanwhile, I had recently been working with Ruby, which is full of developer niceties like odd? and even? . Naturally, I thought it’d be nice to be able to bring something similar to Swift:

.filter { integer in
    integer.isEven
}

.skipWhile { integer in
    integer.isOdd
}

.takeWhile { index, integer in
    integer.isEven && index < 3
}

A small nicety makes this kind of code a lot clearer and less error prone.

The Pitch

The next step was to write a pitch. A pitch is the first part of the Swift Evolution process: you can share a sketch of your proposed changes on the forum, and receive feedback from the community.

While writing a pitch for isEven/isOdd , I discovered someone had already started a pitch thread with the same idea, but progress on it had stalled. I considered starting a fresh forum thread, but decided to try reviving the original one with my own spin on the topic. I added my pitch based on the new post-toggle guidelines, and made some coffee.

Me: This shouldn’t be too controversial!

Narrator: It was.

One by one, responses started popping up. Swift core team members were chiming in. It was definitely exciting, but also surprisingly nerve-wracking. By the end of the pitch phase the thread had over 120 replies and over 5000 views, making it one of the top 10 most active pitch threads all-time on the forum. I don’t think anything I had ever written before had received so much feedback.

The responses were mostly favourable, but there were some concerns. The biggest was that isEven and isOdd didn’t address a big enough problem for inclusion in the standard library. Some folks didn’t seem to appreciate the value in moving away from the % operator – a move that helps avoid subtle bugs when working with negative values, especially due to differences between how % works in other programming languages.

The most common suggestion overall was to include a more general solution to the problem of integer divisibility. I was originally hesitant to expand the pitch because I couldn’t recall many times when I needed check for divisibility other than two (even/odd). I surveyed the source code of a variety of open source projects and found that 20-40% of divisibility testing was done for values other than two, so there was definitely a use case. The next thing you know, isEven/isOdd became the isEven/isOdd/isDivisible(by:) .

The Trouble With Zeros

The next issue at hand was getting correct handling of zeros with isDivisible. A typical mathy definition of divisibility usually looks something like:

If a and b are integers with a≠0, then we say a divides b if there exists an integer k such that b = ka.

Given the definition of divisibility, some argued that someNumber.isDivisible(by: 0) should be a precondition failure that crashes – but few developers would actually want that in practice.

On the other hand, relaxing the definition of divisibility to allow checks for divisibility by zero leads to a different strange situation: what if both values are zero?

let x = 0
let y = 0

// Zero is divisible by zero, according to the relaxed definition of divisibility.
if x.isDivisible(by: y) {
    // Crashes because division by zero traps in Swift. Divisibility != divideability
	x / y 
}

Well, that’s awkward. The code looks safe at first glance, but leads to an unceremonious – and likely unexpected – crash.

Thankfully, a community member proposed a clever workaround: rename isDivisible(by:) to isMultiple(of:). This provided the functionality we wanted, without implying anything about an actual literal mathematical division. This small tweak was so perfect and so simple, but it may not have ever come up without this broad public feedback process.

A month into the pitch phase, and it had evolved to be isEven/isOdd/isMultiple(of:) and all that was missing was implementation. Luckily for me, Apple’s Stephen Canon graciously provided an implementation complete with tests. Stephen is one of the most knowledgeable people on the planet when it comes to computational numerics, so getting an offer to use his implementation as part of the proposal was like an awesome, geeky gift from the programming gods.

Meanwhile, on another pitch thread, Swift core team member Ben Cohen posted a message expressing his concern that recent pitches contained “huge quantities of sugar and not enough protein”. This signalled that the isEven/isOdd/isMultiple(of:) review might be an uphill climb.

Review Begins

The first day of the review started on August 20, which coincided with the first day of my summer vacation. The review phase felt like a rollercoaster ride. A string of positive reviews would be posted and I’d be flying high, but then a scathing negative review would be posted which would leave me questioning why I’d even bothered proposing these changes in the first place. Well-known names in the Swift community like Erica Sadun and even Chris Lattner voted “-1”. My confidence sank, but I still stayed up each evening, responding to and addressing concerns that had been posted that day.

More than anything, the thing that surprised me was how passionate users were on each side of the proposal. This modest proposal, which started as a clear way to test if a number was odd.

Jordan Rose, another Swift core team member, pointed out that the proposal was a victim of Parkinson’s Law of Triviality:

Members of an organization will give disproportionate weight to trivial issues.

I suppose that’s what I get for making my first pitch something small. 😅

At the review’s conclusion a week later, supporters outnumbered opposers by roughly 2:1. It looked like isEven/isOdd/isMultiple had a chance of being accepted – but it was far from certain. I felt relieved that it was out of my hands, and I got back to enjoying my vacation. 🎣

A Decision Is Made

On August 30th, 7 weeks after my initial pitch post, the Swift core team announced new criteria for additions to the standard library:

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.

Stern, but fair. Some examples of such advantages from the new guideline:

  • 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.

In the end, the core team felt that isMultiple did meet the “fluency” criteria. It could prevent bugs around negative remainders, and could have performance benefits for large integer types. The addition of isMultiple was accepted, but given the addition of isMultiple, isEven and isOdd did not cross the threshold for inclusion in the standard library.

And so the chapter closed, and with success! Well, partial success.

The decision rationale made sense. Still, a small part of me still felt like there was a certain je ne sais quoi about even and oddness that made isEven and isOdd worthy of inclusion too. But it was not to be.

Lessons, Odd and Even

I learned a lot at each phase of the process, and appreciate everybody who took time to help get this proposal into Swift.

For those considering a Swift proposal of their own, here are my key takeaways:

  1. It will take more effort than you expect. Writing the initial pitch is the easy part. Reading and responding to feedback, especially critical feedback, takes a lot of time and mental energy.
  2. Unexpected things can threaten to derail the entire discussion. My initial pitch included offhand comments about how value.isEven is fewer characters than value % 2 == 0 and that operator precedence rules impact the readability of statements that include the % operator. Both came back to haunt me repeatedly during discussions. Many users (rightfully) rejected the idea that API changes should be driven by character count and disputed my operator precedence claim. You should focus on the key selling points in your pitch, and cut everything else to avoid wasting time discussing weak side-points.
  3. You need an implementation. It wasn’t clear to me at the start of the process that an implementation is required in order to be reviewed. I was fortunate to receive a high quality implementation from Stephen Canon but you can’t assume someone will provide it for you. It’s not as scary as it sounds though! Building Swift from source was easier than I thought it’d be, and I don’t think it would have taken me too long to actually make the changes, if I’d needed to.
  4. Don’t take things too personally. Your pitches and proposals may be criticized harshly, but that’s part of the process. Be gracious when receiving feedback, and try to keep an open mind. The Swift forum is full of really smart people who want the same thing as you: a better programming language.

isMultiple(of:) will be part of Swift 5, which is scheduled for release sometime early in 2019. Despite it being more work than I expected, I’m glad to have been a part of the process. If you have an idea for an improvement to Swift – and can avoid running afoul of Parkinsons’ Law – then consider making a pitch of your own. ⚾️

Rob MacEachern • Developer