Read more of this story at Slashdot.
Read more of this story at Slashdot.
Read more of this story at Slashdot.
I’m not sure if I’ve said it on my blog, but I’ve said it in person to other people: Swift’s type system solves a problem I don’t have.
Example: if I make an NSArray, I know what’s in there, my code knows what’s in there, and I’ve never had a crash or other bug where the array contained something unexpected.
Seriously. This isn’t that hard.
Except that that’s not actually true.
* * *
There are two cases where that hasn’t been true, and they’re representative, I think.
One is in JSON parsing: I’ve ended up with NSNull in an array or dictionary where I expected something else — and then I’ve sent it a message expecting it to be an NSString or whatever, and the app crashed.
So that’s a thing: data from the outside world may be weird.
That being said, Swift doesn’t necessarily help very much. Or maybe it does, but I’m not good enough at Swift yet to realize how it helps. Totally possible. But right now, for me, there’s always a point where I’m forcing a cast and then looking around nervously to see if the atmosphere ignited.
There’s a second case where I think it does help. I wrote a crashing bug in Objective-C (and fixed it moments later; it didn’t ship) where I expected everything in an NSArray to be of the same type, and it wasn’t true.
The code was building a pull-down menu, and the zeroth item was an NSDictionary with a value for key @"name", and other items were of an object type that had a name property. Every object would respond to valueForKey:@"name" as expected — but not to obj.name. I didn’t realize what was going on at first, and Swift absolutely would have helped.
Now — the original code wasn’t written by me, and I wouldn’t have done it that way, but it’s not necessarily wrong. Just different from my style.
But it’s a fact of life that apps very often have more than one author, and sometimes many more than one, and with different styles. And they may be separated in time (the original author may not even be working on that app; they may not even be at the company any more).
While Swift makes it harder to write type-related bugs — which is great — it also makes it harder, sometimes, to do what I want in a reasonable and simple way, which is not great.
However, it improves with each release. And if there are trade-offs which make the choice of language a tie, the tie-breaker has to be the question: which technology is the future?
(That presumes that the choice of language is a tie. It may or may not be. It differs with each person and each app, and it changes with every release of Swift.)
Read more of this story at Slashdot.
Let’s say I’m writing an RSS reader. (This is the example I tend to use on my blog, for historical reasons, and shouldn’t be taken as indicative of anything.)
Let’s say the RSS reader can have a mix of stand-alone feeds and feeds that are synced with FeedBin, Feedly, NewsBlur, etc. I might very well do this:
(Why? Because each syncing system is different, and rather than have a giant Feed class that can handle all the different types, it’s smarter to have a Feed protocol and then specific implementations for each different type of feed.)
RSS readers tend to have folders too. But folders may have different rules, depending on the system: folders inside folders may or may not be allowed, for instance. So, similarly, I might do this:
The Folder protocol includes the following:
var feeds: [Feed] {get}
func addFeeds(feedsToAdd: [Feed])
Now, in a concrete implementation of addFeeds, I want to check that each feed isn’t already contained in the folder.
func addFeeds(feedsToAdd: [Feed]) {
for oneFeed in feedsToAdd
if !feeds.contains(oneFeed) {
feeds += [oneFeed]
}
}
But I can’t: I get Cannot invoke 'contains' with an argument list of type '(Feed)'.
Okay, I think — let’s just use a Set anyway. Probably should have been a Set all along.
So I change the Folder protocol to make feeds a set:
var feeds: Set<Feed> {get}
And on that line I get an error: Type 'Feed' does not conform to protocol 'Hashable'.
So I try to make the Feed protocol Equatable, and I can’t. (And it has to be Equatable to be Hashable.)
* * *
I believe in protocol-based programming. Big, big fan. But it’s here where I get frustrated.
It occurs to me that I’m still new to Swift, and I’m trying to use Objective-C patterns. That’s fair and true.
The Objective-C version of all of this is pretty natural, though. Given Feed and Folder protocols, I can do this:
- (void)addFeeds:(NSArray *feedsToAdd) {
NSMutableArray *feedsArray = [self.feeds mutableCopy];
for (id<Feed> oneFeed in feedsToAdd) {
if (![feedsArray containsObject:oneFeed]) {
[feedsArray addObject:oneFeed];
}
}
self.feeds = [feedsArray copy];
}
(A version where self.feeds is an NSSet is even simpler.)
It occurs to me that there probably is an answer in Swift. But it probably means more code. Objective-C syntax may be more verbose, but the fact that I can treat id<Feed> as an object without having to stand on my head is important because, again, I’m a huge fan of protocol-based programming. (And I’m a huge fan of less code.)
Let’s just say that I’m doing it wrong. What’s the right way to do this?
* * *
Aside:
“Brent — if you love Objective-C so much, why don’t you marry it?”
Here’s the deal: I’m 47 years old, and if I start ignoring new things, I’ll fall behind and won’t be able to catch up. When you’re 30 or even 40 you can safely ignore things for a while and catch up later. Later on it’s harder and time is shorter. So I’m learning Swift.
And, by the way, I’m enjoying it. There’s so much to love. But sometimes I hit roadblocks and Swift’s type system feels like a straightjacket — and then I miss the elegance of my beloved Smalltalk-derived Objective-C.
* * *
Update 11:25 am: I created a playground showing my conundrum: RSSReaderExample.playground.zip. (May require Xcode 7 beta.)