Swift operators are flexible and powerful. They’re symbols that behave like functions, adopting a natural mathematical syntax: for example,
1 + 2 versus
add(1, 2). However, it’s important that you treat them like potential Swift Kryptonite.
In this talk from the Swift Language User Group, Erica Sadun discusses why your operators should be few, well-chosen, and heavily used. There’s even a fun interactive quiz! Play along with “Name That Operator!” and learn about an essential Swift best practice.
My name is Erica Sadun. For those of you who don’t know who I am, I’m a writer, I’m a blogger, I’m a coder, and I particularly write about the Apple ecosystem.
My latest book is called called Swift Style. It’s in beta over at Pragmatic Press, and it’s going to print in a few weeks.
Why does style matter? Why, when you code, should you even bother thinking about style, reviewing your style, or improving your coding style?
The compiler doesn’t care about your code. Style isn’t about how the compiler sees your code, the correctness of your code. Style is about people.
It’s not just the compiler that needs to read your code. Good style is about not just working with other people, but working with yourself and your future self who’s going to look at this code. You’ll increase the understandability and maintainability of your code by introducing good style.
I’ll cover a few practical and pragmatic concepts, and I’ll discuss how both coding and people work.
Operators Are Awesome (2:39)
What are operators and how do they work? Operators are symbols, but they behave like functions. Unlike normal functions, they have an integrated syntax. They let you check, change, and combine values using a notation that’s natural and mathematical.
1 + 2 addPair(1,2)
These calls perform identical tasks: it’s simply adding two numbers. The operator version uses the plus sign and it puts it right between two numbers. This is called infix form. It’s simple and easy to read. It has a natural correspondence to math: you write your code just like you’d write an equation.
Below it is the functional version, which takes you a step back from the direct expression. This version has a custom name. It lists its arguments inside parentheses, and it’s wordier. It feels more constructed and less natural than the operator version.
Get more development news like this
I think the operator version is easier to read and is easier to understand when you glance at the screen because it’s so close to the way we were taught to write when we did this by hand.
In general, operators are awesome. They’re readable, they naturally map to your established conventions that you’re already trained in, and they enhance the clarity and intent of your code.
But, are they always a win? Like any language construct, operators have both benefits and of course draw backs. There are right and proper places for operators, and then there are times when operators are simply the wrong choice.
The key is knowing how and when to use operators properly.
Swift’s operator suite
For simple and universal operations, it’s hard to beat operators. Swift has its built-in operator suite. It has Boolean logic, like the logical
&& operator. It’s familiar and easy to use.
Swift also offers bit operations, like the shift operator
<<. It’s a strong and obvious member of the Swift operator suite. Swift also lets you use operators in ways that aren’t directly mathematical. You can form ranges with an operator:
These operators are powerful, expressive components of the language. Swift doesn’t limit you to the built-in suite of operators. It enables you to create your own custom operators that perform almost any functionality that you can build with one or two arguments. Custom operators allow you to expand your expressive reach.
For example, you can introduce a double-star operator:
**. I use this for exponentiation. This operator is ridiculously simple to implement, and it picks up an existing convention that you may have seen in other languages. By creating a custom version, you introduce that existing convention into the Swift ecosystem.
More than just math
Custom operators aren’t limited to math. For example you might build a custom operator that performs a safe bitcast:
-->. In this case, it ensures that the size of the destination type matches the memory layout of the item that you’re casting. That’s an extra check that’s not built-into Swift itself which adds functionality and value into the language.
If you’re feeling particularly transgressive, you can even bring back the prefix and postfix increment and decrement operators that Swift got rid of last year:
The decision was made before they open sourced the language. Now, if you want to check out this kind of customization, I actually put in my own implementation of the prefix and postfix increment and decrement operators.
Custom operators pull from a huge set of legal typographic symbols. You might create your own set union operator with this corresponding unicode symbol
∪ and use it in place of the built-in union function that’s provided by Swift’s standard library.
By using a custom operator, you get a powerful way to apply functions without using the function notation.
let set1: Set<Int> = [1, 5] let set2: Set<Int> = [2, 5] let set1: set1 ∪ set2 print(set3) // [1, 2, 5]
As you see in this example, on the third line, I’m using a custom operator.
In the same vein, you could build a subset operator. Again, you would be using standard math symbols and you use the magic of Swift in unicode:
⊆. Set operators offer a natural correspondence to set mathematics. However, the reality is it could also be a real pain to type.
How to type custom operators
If you’re working on MacOS (which is actually not a given if you’re working on Swift), you can wrangle the system character picker to find and type your unicode for you.
You might also think to build some keyboard macros. Unfortunately ,the shortcuts you create in System Preferences will not expand in Xcode. If you want a keyboard solution, you’re going to need to look at third-party solutions. Keyboard maestro, or some other third-party solution, for example. This simply counters the effort and pain involved in typing custom operators.
Consider the context
Is this code going to go open source? Is it going to become a library to be consumed by third-parties? Are they going to have your macro tool sets on hand?
What’s easy to type at your office may not be easy to type for a well-distributed library. The first rule of Operator Club is: If it is hard to type, it is hard to use.
No matter how mathematically perfect and correct and semantically beautiful your symbol, if it is hard to get that symbol into your code, you are sacrificing utility for pretty, which is not a good thing.
You have to understand how much effort is going to be involved in creating these beautiful operators, and use that as a guideline for whether or not you should include them.
Here’s another rule of Operator Club: Not everybody uses a US keyboard. Symbols that are easy to type on a US keyboards can be ridiculously difficult to type on international keyboards. Don’t assume that everybody’s layouts are the same.
Should You Use Operators? (13:58)
Understanding how human factors play into programming helps you establish good coding practices and that’s what introduces good style into your code. Operators rely on two key elements of human cognition: recognition and recall.
Recognition is all about familiarity. When you see an operator in your code, will you recognize what it does? You see it and you just have to confirm that either, yes, this is the right symbol, or no, it’s not the right symbol.
Recall is much harder than recognition. Recall relies on retrieving information from your memory by associating that information with the related concepts that you have easily available.
Recognition always has a lower cognitive burden than recall. It’s easier to recognize that a fact is correct than to recall the answer to a question.
As a rule, recognizing a function name is always easier than recalling an operator, unless the operator is well trained in your mind and universally understood, like the plus sign.
Recall is hard, and operator recall is especially hard. No matter how pretty your operator, it places extra intensity on the burden of recall.
Operators lack context
Operators, especially custom ones, naturally lack the context and cues that allow you to relate information and functionality to things that you are seeing in code as you look at it.
Code review is very often done offline, on paper, where you don’t have access to Xcode or its quick help system. Additionally, there is a significant and growing number of Swift developers who work on platforms like Linux or use non-standard development environments that may not support the structured document presentation of Xcode’s quick help. Xcode is engineered to support association, which allows you to link the related concepts together by use of quick help and other tools, but it’s not always there on other platforms.
Association helps you to narrow down concepts by relating terms to an idea. Most of all, it assists recall. In Xcode, an associated word feeds into its auto-completion system in a way that pure symbols can’t. If you’re struggling to remember how to perform a subset, which is a very standard set operation, nearly every coder will find it more easily by starting to type an associated term (i.e. the word “subset”) than trying to remember and type the symbol which would never be able to use this association system.
Unnatural operators aren’t naturally recognizable. Put yourself into the shoes of someone reading your code and not the person who wrote the operator. For someone completely fresh to it, there’s no implicit or expected recognition for an ad hoc operator that is not pulled from math or science.
Someone will read your code
The compiler isn’t the only one who’s ever going to read your code. You are going to read your code as you write it, and you’re going to read it in a month or a year from now. Future-you has to maintain the code that you wrote some time ago. People in your organization and anyone who consumes your APIs will also read it. For anyone who encounters your operator, an arbitrary operator has no natural meaning outside the structured documentation you used to annotate it.
Reinforcement means integrating a concept into your accessible memory through repetition and through the reward of using it properly. When you apply an operator and your code works and keeps working and every time you use it, it offers positive reinforcement.
People don’t struggle with conventional symbols. You don’t have to struggle to remember that the plus sign means addition, since you use it constantly. You’ve learned it since you were a kid, and it appears on every calculator on earth. It is no less learned than the union operator or the subset operator, but it is more reinforced.
To support your recall, your operator needs that reinforcement. You must use an operator regularly, meaningfully, and in a way that drives it into your mental tool set. So, don’t bother creating operators unless you’re going to use them a lot. The more you use it, the more you’ll retain it.
Lessons Learned (32:06)
Operators are precious and expensive. They involve significant mental costs for creating code for reading code, and maintaining code. Use operators sparingly.
An operator that is sourced from foundational concepts like math establishes important context and association cues. These cues reduce your training costs. They enhance mental retention. Always prefer natural operators like mathematical symbols to wholly fabricated ones.
If you’re going to dive in and use an operator, select non-trivial functionality with the biggest impact possible. There are very few operators that travel with me from project to project, but when they do, they are the operators that work and that they offer immeasurable improvement in safety, reliability, efficiency, and simplicity. Always pick operators that matter.
A great operator that’s impossible to type isn’t going to get used. You’ll just end up writing a function that calls the operator and that completely blows past the point of creating the operator in the first place. Keep your symbols simple and keep them easy to type.
Think global. First, prefer symbols that can be as easily entered on the German keyboard as a US keyboard. Also think about when you’re shipping libraries outside your house development, keep them as meaningful for your consumers as they are for the people who are producing those libraries.
When you adopt an operator use it, use it! If you’re not using an operator significantly more than you would a function, seriously consider cutting it out and just replace it with a function. There is no shame. You’re going to decrease your training and lower your maintenance costs.
Sometimes we fall in love with the idea of an operator more than the reality of it. If you want your Swift code to look like a kind of type set math paper, you are prioritizing the wrong goals. Prefer usable code to pretty code.
I’ve talked about recognition, recall, retention, association, recency, context, and more. These concepts are super useful for evaluating operators, but the same human factors apply to all your code.
Everything in style basically boils down to these lessons. You have to learn to consider your code in terms of the effect it has on the people who read it, because it’s not just the compiler who reads it. Other people will maintain, expand, and use your code in the future. Understanding how humans think and process information is the core idea behind all programming style, and it is the single touch point that every style guide serves.
Good coding and good style serves the human as well as the compiler. Good style is all about working with people.