As
Rob Napier wrote,
we do not know Swift . And that's okay - in fact, it is even great: we have the opportunity to decide for ourselves what this young world will be next. We can (and should) look to similar languages ​​in search of ideas, although many good-practice practices are more preferences of the community than objective truth. Judging by the
long and
tense conversations in the developer forums about when and how best to use optional types, I increasingly prefer not to get involved with them.
Optional types are the same tool as everyone else. According to the habit that has been fixed on Objective C, we use
nil
where it doesn’t get - as an argument, default values, boolean values, and so on. Using the
nice syntax for optional types , which Swift gives, you can turn almost anything into an optional type, and work with it almost the same. Since the optional types are
unpacked implicitly , everything is even simpler: you can use them and not even guess about it. But the question arises - is it reasonable?
I would say no. Even seeming ease of use is deceptive — Swift was designed as a language without
nil
support, and the concept of “lack of meaning” was added as an enumeration.
nil
not an object of the first kind. Moreover, working with several values ​​of an optional type in one method often leads to a code that you cannot look at without tears. When something was so fundamental in Objective C, and now it is being expelled from the list of objects of the first kind, it is interesting to understand the reasons.
')
Let's start with an example where optional types fall into place. Here is long known to us error handling in Objective C:
NSError *writeError; BOOL written = [myString writeToFile:path atomically:NO encoding:NSUTF8StringEncoding error:&writeError] if (!written) { if (writeError) { NSLog(@"write failure: %@", [writtenError localizedDescription]) } }
This is a confusing situation, which optional types help clarify. In Swift, we could write better:
// var writeError:NSError? = myString.writeToFile(path, atomically:NO, encoding:NSUTF8StringEncoding) if let error = writeError { println("write failure: \(error.localizedDescription)") }
Here the value of the optional type perfectly describes the situation - either there was an error or nothing. Although ... is this true?
In fact, not “nothing happened”, but quite successful data writing to the file took place. Using the
Result
listing I
wrote about earlier , the code value would correspond to the record:
// var outcome:Result<()> = myString.writeToFile(path, atomically:NO, encoding:NSUTF8StringEncoding) switch outcome { case .Error(reason): println("Error: \(reason)") default: break }
There is more text, but it is clearer: we check the
result of the operation , but it can be successful or unsuccessful
1 . When I see an optional type and stop thinking in categories of Objective C, I increasingly understand that its abstractness hides the essence of what is happening from us.
In my understanding, the type system is a way to give states a meaning. Each type has a value — an array conveys a data sequence, a dictionary — a relationship between two views, and so on. When viewed from this point of view, the optional types describe one rather specific case — when the presence of a value and its absence are important in their own right. Interactive I / O can be a good example: a user can enter data, but can not, and both states are equally important. But more often it happens that the lack of meaning says something more
2 .
When I get the idea to use a generic type, I meet her skeptically. Often, the lack of meaning that I am trying to express really means something else, and the code gets better if a special type is described for this absence.
• • • • •
On the other hand, Swift code can interact with Objective-C code, and there the transfer of
nil
prohibited only by a convention, notes in the documentation and unexpected drops during the operation of the program. Such is life, and the ability to use the wonderful Cocoa library more than compensates for these inconveniences — but this does not mean that optional types should be thoughtlessly released beyond the interactivity layer.
For example, let's try to write an extension for
NSManagedObjectContext
. In Objective-C, the signature would be something like this:
- (NSArray *)fetchObjectsForEntityName:(NSString *)newEntityName sortedOn:(NSString *)sortField sortAscending:(BOOL)sortAscending withFilter:(NSPredicate)predicate;
If you try to access this method from Swift, the signature would look like this:
func fetchObjectsForEntityName(name:String?, sortedOn:String?, sortAscending:Bool?, filter:NSPredicate?) -> [AnyObject]?
This is an absolutely absurd signature. To understand, let's start with a few assumptions:
- Entity name is always needed
- We always want to get a result, even if it turns out to be empty.
Knowing that Core Data always returns
NSManagedObject
us, we can make the signature more meaningful:
func fetchObjectsForEntityName(name:String, sortedOn:String?, sortAscending:Bool?, filter:NSPredicate?) -> [NSManagedObject]
Now let's take a look at the two parameters responsible for sorting. The two optional types are a terrible choice, since their values ​​are somehow related. We, maybe, want to sort something, but we still have to specify the sorting order. To do this, turn to the good old listing and write the following:
enum SortDirection { case Ascending case Descending } enum SortingRule { case SortOn(String, SortDirection) case SortWith(String, NSComparator, SortDirection) case Unsorted }
Of the five optional types left alone. In addition, the sorting rule has become more expressive, since now it is possible to use a closure. The last remaining optional type conveys exactly what is required - the filter is either there or not. It is possible, of course, to rewrite it as a separate type (at first I did that), but the advantages turned out to be insignificant.
After rethinking our understanding of the principle of the code, we threw out four unnecessary optional types, simultaneously making the code more visual. This is an undoubted success.
• • • • •
To summarize: I believe that optional types are needed, but not at all as often as it might seem in terms of their ease of use. The reason for this ease is that you need to interact with the code in Objective C. If you wrap up every single parameter of an optional type in enums, you will not be able to use it, but do not write in Swift as if you are still writing in Objective C. It is better to take the most useful concepts. , which we learned in Objective C, and improve them with what Swift offers. Then the result will be something really powerful - with optional types only in those places where they are really needed, but not beyond that.
Notes1. In the
Swiftz project
, a more competent Result type has
been declared for interacting with Cocoa, in which an error is represented by an object of type
NSError
, and not just a string. I would think that they use a less meaningful
Value
label instead of
Success
, but if you want to write real code, you should probably use this library.
2. There would be an excellent example of implicitly unpacking optional types for
IBOutlets
: if no value is specified, then the entire application closes as a result of the error, and that should be so. Therefore, it is logical to use
IBOutlets
as if this value is not optional at all.