Question

First off all: sorry for the title, but I didn't now how to better formulate the meaning of my following question in a single phrase.

While I was writing the following Swift code:

if errorData.isMemberOfClass(UIAlertController) {
    self.presentViewController(errorData, animated: true, completion: nil)
} else {
    /* Some other code */
}

for my personal project the compiler gave me this error:

'AnyObject' is not convertible to 'UIAlertController'; did you mean to use 'as!' to force downcast?

and it won't let me compiler any further if I would not change my code in to this:

if errorData.isMemberOfClass(UIAlertController) {
    self.presentViewController(errorData as! UIAlertController, animated: true, completion: nil)
} else {
    /* Some other code */
}

But I don't get this: if I make the program check the class of errorData(which, to be precise, is of class AnyObject) using the if statement why does it force me to force downcast against the type of class is supposed to be inside the if? In other words: doesn't the compiler already knows that inside the statement errorData is UIAlertController? If it wasn't the program continued in the else portion instead, so errorData could even be of class Int or String for example

Was it helpful?

Solution

Yes, the compiler could deduce the fact that the variable is of the expected type, since it was tested immediately before. And in fact this would be the next easiest thing to add to the code for data flow analysis.

However, the compiler will never be able to deduce as much about the program as you can. Even such a (to you) completely obvious property as the type in this construct has to be programmed into it explicitly. No matter how much effort the compiler writers expend on the task, there will always be a condition which seems obvious to human uses but isn't at all obvious to the compiler. (The problem is compounded because human perception works adaptively. Make a compiler smarter, and users will begin to expect smarter things from it. Deductions that last year seemed pretty advanced will now seem routine, and new problems will seem as if "the compiler should easily recognize that".)

Therefore, you are merely observing the current boundary between what's theoretically possible, and what (in the opinion of the compiler writers) is worth doing. Don't be sad. In a couple years they will have added this logic, and by then you'll find something else unreasonable!

OTHER TIPS

There are several different possible answers to this.

Answer number 1 is that isMemberOfClass is a method just like any other method. The compiler has absolutely no idea what this method is doing and what the code means. It might even be overridden in a subclass. The method isn't even part of Swift, it's part of the Objective-C standard library.

Answer number 2 is that, well, maybe the compiler does know, maybe it generates more efficient code because of this, but the language semantics of Swift say that you can only call methods that exist on the static type of the reference. The compiler is not allowed to violate the language semantics. It may use this information for various purposes such as optimization, generating better error messages, etc., but it may not use it to do things the language specification forbids.

Answer number 3 is that there are languages which have a type case or type guard construct, and Kotlin's smart casts have already been mentioned. But notice that in the case of Kotlin's if (errorData is UIAlertController), the is construct is a language-builtin construct for type-testing, so the compiler knows what it means, whereas isMemberOfClass is just a method like any other method, it has no special meaning to the compiler. So, a feature like this could be added to Swift, but probably using a language-builtin type test (is), not a method (similar to if let for optionals).

I don't actually know Swift, but I think you can do something like this:

if let errorData = errorData as? UIAlertController {
    self.presentViewController(errorData, animated: true, completion: nil)
} else {
    /* Some other code */
}

You can imagine that there are some ways to introduce syntactic sugar to shorten this, but not by much.

A language must be well-defined. It would be awful if code was valid or not depending on the cleverness of the compiler. That would lead to code that runs on one compiler and not another.

Remember Java had the requirement that each variable must be defined before it is used. But that wasn't actually the requirement, because that would have led to the problem above. So the requirement was that each variable must be defined before used, in a way that was recognizable by a very well-defined algorithm. You were not allowed to write a more clever compiler that recognized things other compilers didn't.

So the language designers would have to come up with rules exactly when such deductions can and must be made, and that is very, very difficult.

On the other hand, the language has a very simple way to write this code using "if let".

Licensed under: CC-BY-SA with attribution
scroll top