I think this is a distinction without a difference. If you have:
print(obj.count)
inside the scope, you have something of the form (expression).count. Without your proposed change, the expression is of type Any. With your change, the expression (aka obj as! String) is of type String. For all intents and purposes, the type of the expression at the use site has changed, though of course the type of the parameter obj hasn't changed formally at the outer level.
I'm sympathetic to your goal, but I feel there's a better solution lurking just offstage here. We haven't found it yet.
I'm using Kotlin everyday and still find this feature more a burden that something helpful.
It forces writing inconsistent code.
When it doesn't work (often), it forces introduction of a local variable, and when is works, introducing an intermediate local variable is pointless and confusing for the code reader.
And I'm not taking about contract which make guessing when it will work even more complex, as it make some functions magically work (functions with contract annotations).
If I where asking for a change, that would be on the Kotlin side, not on the Swift side. Kotlin lacks a clean canonical way to "unbox" optional or perform "check cast". If it had one, I would advise to always use it for the sack of code consistency and forget the implicit behaviour.
@QuinceyMorris This has been asserted a couple times in the comments above, but it's not accurate. The fact the variable's type has not changed impacts, for example, method resolution:
func talk(_ obj: Any) {
if obj is String {
printMe(obj)
}
printMe(obj)
}
func printMe(_ obj: Any) {
print("\(obj) is Any")
}
func printMe(_ obj: String) {
print("\(obj) is String")
}
talk("Hello")
// Output:
Hello is String
Hello is Any
I appreciate you sharing your experience using it with Kotlin. That is completely inconsistent with my common case, but I obviously can't argue with what you are experiencing.
I use it all the time when working with parameters inside of switch and when, and have never been confused by it. You can certainly create convoluted use cases, but as I mentioned above, the same is true for many Swift features include fundamental things like type inferencing with array and dictionary literals.
Why is the output different if the type didn't change? In today's Swift the type doesn't change, and it prints "Hello is Any" twice. What would you expect if the type DID change?
Because it is cast to a String the first time due to it being inside the if obj is String statement's block. Try applying an as! String to an instance of Any when you call printMe. Function resolution will pick printMe(_ obj: String) if you call it again without the cast it will select printMe(_ obj: Any). That is all that is happening.
I see @Avi beat me to the answer, but I'm leaving my response as well in case the additional explanation is useful. I've clearly created some confusion about how this works. Apologies if I didn't explain it well.
I believe that's an incorrect perspective. That cast will fail at runtime if the type isn't already String. It is not correct to say the type is being changed.
Are we talking about static type (the one by which compiler type-checks your code) or the dynamic type (the one returned by the type(of:) method) ? I thought that we're talking about static type in this thread. as! changes the static type.
Standard Swift overload resolution is based on static type, not dynamic (unless we're using protocol requirements, closure properties, or class inheritance)
It will use String's map method. The method resolution is clear, and already defined. Within your if block it will use the method defined in the implicit cast (in your case, as! String). Note that you could have also tested for nil to product the same effect.
? This isn't in anyway specific to Any. Also, people who write interpreters, code analyzers, compilers, frameworks and many other things frequently have to deal with Any.