This isn't the same thing as "magically" changing the type.
What does the syntax technically mean?
What refinement is undone?
I think you're a bit too wedded to the idea that there's a type conversion going on or some dark magic or something behind the scenes that's "done" then "undone" like a mechanism. There isn't. It's just static analysis. Same as:
let x: Animal;
x = Dog()
if (x is Dog) {
x.bark()
}
The Zen koan you want to ponder on your end is, why do you want to A) eliminate polymorphism from OOP B) remove the safety of a compiler error if the code is changed to x = Cat()?
I don’t like that either. x is Dog is a boolean expression. Presumably I can write
let isDog = x is Dog;
And the value whether it is a dog or not goes into that new variable. The fact that I can’t then immediately go
if (isDog) {
x.bark()
}
shows the deficiencies of this static analysis (even if you could do some simple value tracking to make this work, it doesn’t really address the real issue I have with it, which I described above: why can’t I do this refinement for other things?) The conceptual model is one of “we special cased 2-3 cases that we can detect”, which I don’t find very elegant, especially considering that other languages seem to have better solutions that express the operation in what I feel is a better way.
(The equivalent Swift code would be something like this:
let x = Animal()
if let dog = x as? Dog {
dog.bark()
}
I see this as much superior. One, because x is still available in the scope of I want an Animal and not a Dog for whatever reason. I understand that most of the time you don’t do this but to have it available and not have to do a special case for it is nice. The second thing is that I just like the syntax, since it gives you an opportunity to give a more specific name in that branch as part of the assignment. Third, it composes, because none of the operations are special save for the “if let” binding. This code is also valid:
let x = Animal()
let dog /* : Dog? */ = x as? Dog
if let dog /* = dog */ {
}
The static analysis for undoing polymorphism is the exact same as the one for binding to optionals, because the idiomatic way to cast results in an optional.)
Who said you can't? :) This actually works in Dart:
Dog? dog;
bool isDog = dog is Dog;
if (isDog) {
dog.bark();
}
i.e. boolean variables which serve as witnesses for type judgements are integrated into promotion machinery.
I do agree with you that
1. In general there is no limit to developers' expectations with respect to promotion, but there is a limit to what compiler can reasonably achieve. At some point rules become too complex for developers to understand and rely upon.
2. Creating local variables when you need to refine the type is conceptually simpler and more explicit, both for language designers, language developers and language users.
I don't hate excessive typing, especially where it helps to keep things simple and readable - so I would not be against a language that does not do any control flow based variable type promotion. Alas many developers don't share this view of the world and are vehemently opposed to typing anything they believe compiler can already infer.
Kotlin can't do this, which the language I have more experience with. It's good that Dart does a little better in that regard. And, I think we do agree, I just don't really like the viewpoint of doing this, because I feel like it's not really general enough.
I don't think any of that has to do anything with static analysis deficiencies. The analysis is the same. What Swift requires is for the user to manually help the compiler with a rather verbose and otherwise unnecessary construct.
It’s not a new idea, but I don’t think it’s a good idea. That’s just it. Swift has a construct to do the equivalent and it’s fewer characters to boot when compared to Dart when you’re going from an optional to non-optional type.
But that's exactly what Swift does: it narrows the types. To do that, it uses a separate construct that only exists to make the parser ever so slightly simpler.
Where does it break down?
This isn't the same thing as "magically" changing the type.
What does the syntax technically mean?
What refinement is undone?
I think you're a bit too wedded to the idea that there's a type conversion going on or some dark magic or something behind the scenes that's "done" then "undone" like a mechanism. There isn't. It's just static analysis. Same as:
The Zen koan you want to ponder on your end is, why do you want to A) eliminate polymorphism from OOP B) remove the safety of a compiler error if the code is changed to x = Cat()?