Only one year old, Swift is still very much a young language. It is often said that the future belongs to the young, but what about the present? Certainly, Swift is a very capable tool, but so was its predecessor, Objective-C. In this talk, Jack Nutting considers what we might lose if we think of it as the only tool worth using. Better to think carefully about our problems, he suggests, before we decide on the means for their solution.
Background (0:00)
The title of my talk is let swift: Race?
. The meaning may not be obvious. It references a Bible quote, specifically a section of the Old Testament called Ecclesiastes, verse 9:11.
Again I saw that under the sun the race is not to the swift, nor the battle to the strong, nor bread to the wise, nor riches to the intelligent, nor favor to those with knowledge, but time and chance happen to them all.
— English Standard Version
I have translated this excerpt into a nonsensical Swift structure. Not uncommon in the bible, the quote is about an old man spreading wisdom. His life experiences have shown him that many of our efforts are subject to chance, and we often can’t decide what happens. Pessimistic, perhaps.
struct Sun {
let swift: Race?
let strong: Battle?
let wise: Bread?
let intelligent: Riches?
let knowledgeable: Favor?
init() {
swift = TimeAndChance() ? Race() : .None
strong = TimeAndChance() ? Battle() : .None
wise = TimeAndChance() ? Bread() : .None
intelligent = TimeAndChance() ? Riches() : .None
knowledgeable = TimeAndChance() ? Favor() : .None
}
}
The moral of his story: the race is not always won by the swift. To return to the capitalized Swift, I think it’s fair to say that we’re currently very optimistic about it, even if we don’t know where it’s going. I think it has a bright future, and look! We’re all here to talk about it.
Swift gives us great new ways to tackle many kinds of problems. To put my cards on the table, I’m not entirely convinced that the new ways are always the best; they’re certainly not the best simply because they’re new. A well worn metaphor: if your only tool is a hammer, then everything looks like a nail. Let me twist this metaphor a little. One could say that Swift is like a new box of tools, and when you have a new box of tools, there’s a great temptation to say, “Hey, let’s just use these! They’re probably better, right?”. But we should remember, tools are built with problems in mind, and the new tools may not always match match our old problems. If we think in terms of tools, we might lose sight of the problems; we’ll be back to only having hammers, and only seeing nails.
I’d like to share some of my personal history, partly because I think it gives me a certain bias. I wrote my first Objective-C code in college, on a NeXTcube (remember those?). Before Objective-C, I had written BASIC, Pascal, and C. At that time, I didn’t understand Object Oriented Programming very well at all. It was a mystery — and a frustrating one at that. After college, I started productively coding in Objective-C, a graduate slowly falling in love. Since then, I have worked with a lot of different languages, but I have always returned to Objective-C. Until now, that is. I’ve been doing as much work as I can in Swift, and truth be told, I have written almost no Objective-C code since November 2014. Honestly, there isn’t much that I miss. That being said, I am concerned. There are Swift developers who seem overly eager to use new features in ways that, shall we say, may not be optimal in the long run.
Before I develop this further, I want to state upfront: I still like Objective-C. Not only do I like it, Objective-C still powers the vast majority of software that we run on our Macs and iPhones — that’s a lot of software — and it will be around for a long time, while code is being ported to Swift. Some will never be ported. Hopefully, Apple will continue to extend Objective-C.
There is something I would like to mention, which I know might be a divisive issue: Objective-C has brackets. A lot of people complain about them. In reality, the kind of deep nesting that brackets lend themselves to is not something to be recommended. I myself have tried to keep a personal coding style that doesn’t go more than a couple levels deep, for the sake of readability. But to those who celebrate the recent bracket cull, I think I should mention, the Swift equivalent isn’t necessarily better.
// Objective-C
[[[[[[receiver method] method] method] method] method] method];
// Swift
receiver.method().method().method().method().method().method()
People think of this as chaining, as opposed to nesting, but really, it is still the same thing. There is exactly the same number of parentheses as brackets! In either language, this kind of code is improved by assigning some of the initial results to intermediate values, whether that be Objective-C variables, or Swift constants. This helps make the code more readable, by yourself or others, now and in the future. Often mentioned, but never unimportant: it’s important to bear your future readers in mind.
Static Has Drawbacks (6:51)
Many of Swift’s new features are designed to let you make your applications more static — the sharp amongst you will already have spotted my pun. Synonyms that a thesaurus gives for static include: fixed, stable, steady, consistent. All these have positive connotations. Keep going, and you’ll find some synonyms that aren’t quite as flattering: motionless, frozen, inert, lifeless. These words don’t apply perfectly in our context, but I think that they are still a decent fit. Things that contribute to more stable applications may also contribute to more “lifeless” applications.
Sometimes Enums Aren’t the Answer (7:45)
For years, in C-based languages we have used enum
to mark something’s type. These types are not related to the types that the actual language gives us. In C, enums are just fancy names for integers. Now, in Swift, enums give us these incredible new powers. You can implement methods, conform to protocols, and each individual type can have its own associated data.
Imagine that we’re building a system for drawing a GUI. You might make an enum
that represents a button. It wraps up the Button
type and the values that you use right in one spot. In the example below, we have a button with two types, each with some attached values. When we want to draw the button, we can implement a method that will switch
on the type.
enum Button {
case Rectangular(bounds: CGRect, title: String, font: UIFont)
case RoundedCorners(bounds: CGRect, title: String, font: UIFont,
radius: CGFloat)
}
extension Button {
func draw() {
switch self {
case let .Rectangular(bounds, title, font):
drawRectangleButton(bounds, title, font)
case let .RoundedCorners(bounds, title, font, radius):
drawRoundedRectangleButton(bounds, title, font, radius)
}
}
}
Now let’s extend this example. When we create a button, we give it values that might not make sense in the interface. We want each button to be able to calculate its preferred size. Below is a method that does this, and returns a size. You might end up with half a dozen objects to configure this way, and each would use switch
to determine how to behave correctly.
extension Button {
func buttonOfPreferredSize() -> CGSize {
switch self {
case let .Rectangular(bounds, title, font):
return preferredSizeForRectangleButton(bounds, title, font)
case let .RoundedCorners(bounds, title, font, radius):
return preferredSizeForRoundedRectangleButton(bounds, title, font, radius)
}
}
}
Then the unthinkable happens: requirements change. Your customer proposes another button type for their interface. You add a new case to the declaration, and in each of your methods you add another switch
case
. At this point, you are churning out boilerplate for each new button type. Take a step further, and imagine that your button exists in a framework that is used by other people. What if a user of this framework wants to make a new type of button, and sends you a pull request? If you don’t think that their code matches your idea of the general purpose, that user can only fork the framework. They will be in a never-ending cycle, tracking your framework for bug fixes and other changes, and at the same time, hoping that nothing wipes out their special button. It gets worse if your framework is closed source, in which case the user is really stuck. They can use the button types you provide, or they can just ignore it entirely.
In this case, the solution is simple. Don’t use an enum
for something that ought to be changeable in some way. The entire UIView
hierarchy obviously does not work like this; views can be subclassed and changed as need be. But, as an example, a UIButton
is not the easiest thing to subclass. In general, I propose this rule of thumb: if a large portion of your enum
is switching on the type, you should use a set of classes. Inheritance is not a bad thing, and polymorphism is your friend. Admittedly, this does take away some of the benefits of using an enum
, but ultimately it’s your job to do what is appropriate for your project. Again, using the new thing in Swift just because it’s new is not the right reason to use it. You have to evaluate your needs.
There are things besides enums that I would like to expound on: using struct
instead of class
, and marking classes and methods as final
. These are all techniques that can improve the performance of your applications. I won’t go into great detail about the inherent immutability of structs, and the potential performance gains from making things final
. Those are great, sure, but using these features is to paint yourself into a corner with regard to future extensibility. Every time you finalize a method or a class, you eliminate a potential customization point. The same happens when you use struct
instead of class
. Again, this may be what you want to do, it’s your call.
But sometimes, you don’t really know what you want to do. In general, for any kind of value or system you create, future developers will want the flexibility to extend your system in ways you can’t foresee today. This flexibility plays a role in determining whether a system will become valuable and interesting. The more you can extend things, the more your framework can become useful. You should think twice before you arbitrarily decide which parts of your code will prevent customization.
All of C++ is Premature Optimization (13:48)
Premature optimization is the root of all evil.
- Sir Tony Hoare
There is a tradeoff involved when you mark things as final
, before you know if that part of the code is going to be slow. You are engaging in premature optimization. This quote is sometimes used to argue against any optimization, but that isn’t what I’m arguing. Instead, you should optimize the parts of the application that you can measurably determine as slow. Those parts are almost always in loops. You should write your application, find the parts that are slow, and figure out what you can do to speed up your code.
Talking about these static features brings to mind a language that uses static binding almost all the time: C++. My hypothesis about C++ is that all of C++ is premature optimization. I formulated this about ten years ago, after I had been writing in C++, coming from Objective-C. We were building a cross-platform app, and it seemed that every language feature in C++ was equivalent or similar to a feature in Objective-C. The C++ version often had slightly better performance, but it was invariably more painful to use.
Functional Programming Isn’t a Silver Bullet (15:33)
Swift is still far from being a functional language like Haskell, though it has taken huge strides in that general direction. You can do some great things with functional programming in Swift. That is wonderful, of course, but I think a challenge remains. One of the essential tenets of functional programming is that everything should be immutable. This works very well, but only for certain things. Functional programming is a great way to make a view, if you know that the view is repeatable. But, I think it might be less useful for significantly interactive, or “creative” applications.
One way of dealing with immutability and functional programming is with a technique called lenses. Chris Eidhof wrote a great article that I really recommend you read. The idea, in brief, is that instead of making a property of a struct mutable, you create methods on a struct, and these methods will take the new value you want to assign to a property. A entirely new struct is returned, which is a great way of keeping your structs immutable. The problem that I see with this is that, when you have a struct that represents, for example, all the text in a word processor document, or a graphic with a million pixels, creating a fresh copy of a large chunk of data every time you want to make a change is not, perhaps, the most feasible thing to do.
Functional programming is not something that you can apply everywhere. If you do this, you will run into problems. Thankfully, if you insist on it, these problems can be fun and interesting to solve.
For another angle on this question, I recommend a talk by Gary Bernhardt called Boundaries, where he introduces a technique called “Functional Core, Imperative Shell”. As you can probably deduce from the title, he suggests that you use functional programming for the central work of your application, which, as a nice side benefit, allows very easy testing. Then, you use regular imperative programming techniques for interfacing the results with your GUI. It’s a decent rule of thumb.
Q&A (18:06)
Q: I want to agree with you about not forgetting classes, and using enum
s, and functional programming, but for a slightly different reason. I find inheritance to be a difficult and poor way of building extensibility. It is better to split up monolithic things like a UIButton
into smaller things, like rendering capability and layout capability. Classes are perfect for that sort of thing.
Jack: That is a good point. The composability is also a very good pattern for a lot of these things, and it’s a pattern that Apple doesn’t really apply. They have a lot of monolithic things like the UIButton
.
Q: I use protocols rather than the enum
approach, because when you have a lot of things, it becomes easier to extend with new functions. But with final
, isn’t there a danger that more problems will come from not carefully designing for inheritance?
Jack: That is absolutely true, and also a very good point. This is one of the dangers of inheritance. If people are subclassing your code and overriding at the wrong points, people using the code can definitely mess it up. Balance is another tradeoff issue, where you are trying to figure out the correct things to subclass. I have seen a lot of designers make an arbitrary decision in the wrong way, and be a little bit too safe. But, if you arbitrarily let everything be open, then that is maybe a little too open.
Q: One of the nice things about Swift is the ability to use static types and functional techniques in the core, and classes and dynamic types in the outer shell. So, in your experience as an Objective-C developer, have you found places where Objective-C techniques just don’t work in Swift?
Jack: It would be nice to have more dynamic concepts in Swift. In the past few months, I haven’t hit a spot where I needed something that didn’t exist. There are a lot of neat techniques for things like KVO and Core Data, but they are not necessarily ones that an application developer hits every day. I hope that, as they complete Swift, they will add more of these things. I feel that this is probably the intention. But, personally, in the work that I’ve been doing with Swift so far, I have actually not missed anything from Objective-C.
Q: With Objective-C, you made a controversial point about subclassing and polymorphism. I feel like nothing is really private in Objective-C, because somebody can just expose an internal method in an extension, or override things by subclassing. How does that affect how you feel about finality?
Jack: I think this touches on another point. There is an inherent danger in things not being final
. To give an analogy, programming is like driving along the edge of a cliff. The road is impossible to continue down, but heading towards the cliff edge is death. You have to find a balance in designing your code, so that you expose the correct things. In Objective-C, yes, everything is essentially not really private. In a way, Swift lets you really lock things down. But, if you’re dealing with a framework that has problems, you might be stuck if you are unable to forcibly override. So, here I think I might prefer the Objective-C style of doing things, where, if you are really stuck, you can take care of it.
Thank you!
Receive news and updates from Realm straight to your inbox