What is the "fundamental discrepancy"?
I am not sure what you are trying to do here. self
is an instance and Self
is a type, and as
is the type coercion operator, which is distinct from the operators is
and as?
/as!
.
Could you elaborate more on this? The following are equivalent:
a is B
is true,a as! B
doesn’t crash,a as? B
doesn’t returnnil
.
And none of them is checked at compile time (though analyzer does generate warning for impossible cases).
It’s no exception here.
I know, what I mean
is self is Self
evaluate to false. So we need as! Self
type coercion operator to force cast it.
In what scenario does self is Self
evaluate to false
?
No, as
is the type coercion operator, as!
is a type casting operator. You would use self as! Self
to cast self
to Self
when self is Self
is true
, not false
.
Okay, I've reworked the example code to the simplest possible to illustrate the breakdown. Copy it and experiment running it.
The code compiles fine, the problem happens at runtime. Method setValue()
succeeds while setValueFailing()
doesn't because:
self is Self
== true withSelf
seen as beingChild
(you can debug print)- but behind the scenes the instance is being handled as
Base
which is demonstrated by failingKeyPath
matching
Which brings me to the point that self as! Self
has a clear casting effect and changing the instance type, while self is Self
says "all is good here" – but it isn't.
It's a bit like this:
self is Self
is like confirming that 🍏is 🍏
self as! Self
may work as 🍅—> 🍏
while self is Self
next to it ensures us that it's 🍏is 🍏
... but 🍅!= 🍏
Hence me suspecting it being a logical fallacy. The reason, as understand it, is perhaps more of a naming nature. A better way could be changing the self as! Self
construct to let obj = instance(of: self)
but Swift grammar bunches two different principles together thus introducing confusion. Or it is a subtle compiler bug.
Anyway, the proof is in the pudding. The code isn't working for if self is Self { ...
— what can be done here?
import Foundation
class Base {
}
class Child: Base {
var name: String = ""
var number: Int = 0
}
extension Base {
// Works well thanks to 'self as! Self'
func setValue<K>(_ val: Any, at keyPath: K) {
assert(self is Self) // runtime says 'yes'
let obj = self as! Self // ✅ self is being force cast to `Child`
assert(obj is Self) // obviously true
setValue(obj: obj, keyPath: keyPath, value: val)
}
// Doesn't work even though 'self is Self' evaluates as true
func setValueFailing<K>(_ val: Any, at keyPath: K) {
if self is Self { // ❌ self is seen as `Child` but treated as `Base`
print ("I think that am looking good to go since I am \(self) and \(Self.self)")
setValue(obj: self, keyPath: keyPath, value: val)
}
}
private func setValue<T: Base, K>(obj: T, keyPath: K, value v: Any?) {
switch keyPath {
case let p as ReferenceWritableKeyPath<T, String>:
obj[keyPath: p] = v as? String ?? String(describing: v)
case let p as ReferenceWritableKeyPath<T, Int>:
obj[keyPath: p] = v as? Int ?? 0
default:
assertionFailure("Couldn't match KeyPath: \(keyPath) for type: \(obj.self)")
}
}
}
let child = Child()
// These calls work
child.setValue("Adam", at: \Child.name)
child.setValue(137, at: \Child.number)
// This one fails because given KeyPath doesn't exist in `Base`
// even though `self` is `Child`
child.setValueFailing("Adam", at: \Child.name)
Thanks for explanation for as
and as!
.
Why not just use self as Self
when self is Self
is true
?
What's the difference between coercion
and casting
?
In this thread topic, the key point is using as! Self
trick to solve the problem, as Self
compile failed.
If self is Self
is always evaluated to true, why as Self
failed?
In plain text, self is Self
but self can't be as Self
self must be as! Self
to get really Self, ... looks so weird to me.
PS, may I treat coercion as
as try as
or safe as
; and treat casting as!
as force as
or unsafe as
?
They're similar elsewhere too:
-
self is Self
is just returnsBool
, it doesn't do anything toself
, -
optional != nil
is just returnsBool
, it doesn't do anything tooptional
,
In much the same way self is Self
doesn't convert self
to Self
, a != nil
doesn't convert a
to non-optional.
let a: Optional = 3
if a != nil {
// `a` is still `Int?`
}
if let obj = a {
// `obj` is `Int`
// `a` is still `Int?`
}
...
if self is Self {
// `self` is still `Base`
}
if let obj = self as? Self {
// `obj` is `Self`
// `self` is still `Base`
}
The static type of self
is Base
, regardless of what you do. Even what we call casting is just creating new variable referring to the old self
, but with the new type.
Though there are sentiments to do what Rust do:
if a != nil {
// now `a` is `Int`
}
So it indeed is a sound suggestion, to say the least.
Technically speaking, the compiler has enough information to do that. The language just doesn't choose to do add that exception.
- We're inside
Base
extension, soself
is a variable of typeBase
, - A variable of type
Base
generally can't be converted toSelf
, you needas?
oras!
.
Not sure what try as
is, but as
always succeed (it wouldn't even compile otherwise). You can indeed treat foo as! Bar
much the same way as (foo as? Bar)!
. If foo
can't be converted to Bar
at runtime, you'll force-unwrap a nil
, crashing the program.
That said, unsafe unwrap is not as dangerous as it sounds. If you can prove that it never fails, it's a pretty handy tool. In this case, you know, from human ingenuity, that foo
can always be converted to Bar
. and so there's no point in handling failure case.
Much clear for now. Because Self is Child at runtime.
The confusion point is as
and as!
operation. Or Coercion and Casting operator as @xwu mentioned upthread if self is Self
is true all the time why as Self
failed in this topic.
But after reading your this post, self
and Self
is Base-Child relationship we should use (base as? Child)!
or base as! Child
to make force unwrap conversion and get Child type reference, because base as Child
failed due to base(self) is NOT Child(Self)
for compiler at compile time.
My understanding is that, self is of type Base at compile time, but Self is of type Child at runtime since Self actually is type(of:self). So base to Child operation must need as!
operator to help.
But what is the type of Self
at compile time? Base or Child?
If Self is Base, self is Self
should be true, self as Self
should succeed.
If Self is Child, self is Self
should be false, self as Self
failed and need use self as! Self
instead.
let self = self is Self ? self as Self : self as ! Self
At compile-time, Self
does not have a concrete type. Self
represents “the dynamic runtime-type of self
.”
Specifically, Self
is an instance of the type Base.Type
. At compile-time, there is no way to know which instance will be passed in at runtime. It could be Base
, it could be Child
, if other subclasses exist it could be one of them, and if Base
is open
it could even be a subclass from another module that imported this one.
Hence the value of Self
might be some type that doesn’t even exist when we compile this module. It could be a subclass written 10 years from now in an app that imports this module.
• • •
That’s why you can’t write “as Self
”. The as
operator works at compile-time, to let the compiler know which static type something should be treated as.
But Self
is a purely dynamic, runtime-only concept.
• • •
Here’s a function that might help your understanding:
func staticType<T>(of value: T) -> Any.Type { T.self }
Observe:
staticType(of: Child()) // Child
staticType(of: Child() as Base) // Base
Compared to:
type(of: Child()) // Child
type(of: Child() as Base) // Child
Now you can look at the static and dynamic type of self
within your methods on Base
.
You will find that in an extension of Base
, the static type of self
is always Base
, whereas the dynamic type of self
is always the same as Self
.
• • •
When you call a generic function by passing in a value, the compiler sets the generic parameter to match the static type of the value. In your non-working code, the static type of self
is Base
, so the generic parameter T
becomes Base
.
But the keypaths operate on Child
, so they are not convertible to operating on T
which is Base
.
Conversely, in your working code, the dynamic run-time cast creates a new value with a static type of Self
. The compiler does not know which type that will end up being at run-time, but it still treats it as a distinct type.
So then, passing the new value of static type Self
into the generic function makes T
equal to Self
, and then at run-time when it turns out that Self
is Child
then the keypaths are indeed convertible to T
. Because T
is Self
which dynamically is Child
.
Thanks for your reply~
Two more questions.
The as
operator works at compile-time,
-
how about
as?
andas!
? work at runtime? -
In this topic case, is
self is Self
always evaluated to true? Or when to false?
Yes.
Self
is the dynamic type of self
.
“self is Self
” always evaluates to true in every case. (Unless you do something truly crazy like redefine Self
or self
. Don’t do that.)
It is not at all like that. The is
operator in Swift asks only to compare the dynamic type of the instance on the left-hand side with the type given on the right-hand side.
In your example:
setValue(obj: self /* ... */)
// `self` has static type `Base` and dynamic type `Child`
let obj = self as! Self
setValue(obj: obj /* ... */)
// `obj` has static type `Child` and dynamic type `Child`
There is no bug and no two principles. I think you are still not grasping the distinction between the static and dynamic type.
By far the best answer , thank you for actually taking time to work through the code and offering a practical approach!
This function of yours staticType(of:)
truly works and offers the sorely missing bit of logic that would make the code understandable just by reading it. The code is tested and it works:
func staticType<T>(of value: T) -> Any.Type { T.self }
func setValue<K>(_ val: Any, at keyPath: K) {
print("Static type of self:", staticType(of: self)) // Base
print("Static type of Self:", staticType(of: Self.self)) // Child
print("Dynamic type of self:", type(of: self)) // Child
print("Dynamic type of Self:", type(of: Self.self)) // Child
if self is Self && staticType(of: self) != staticType(of: Self.self) {
setValue(obj: self as! Self, keyPath: keyPath, value: val)
} else {
setValue(obj: self, keyPath: keyPath, value: val)
}
}
And this also illustrates the language problem I was referring to. It's not about not understanding static vs dynamic as some other posters suggested. I do understand that stuff. What I am pointing at is the fact that (in this case) Swift doesn't have an explicit way of making the code understandable by reading it.
Truly the construct of self as! Self
is a bandaid that masks the reality by applying esoteric-looking mumbo-jumbo.
All it would take to fix that situation is having an instance(of:)
method just like in Objective-C, so all that code and unnecessary intellectual struggle will turn into simple, expressive, understandable and explicit:
func setValue<K>(_ val: Any, at keyPath: K) {
setValue(instance(of: self), keyPath: keyPath, value: val)
}
Simpler things work better because they require less explanation. As opposed to this thread essentially dealing with a non-issue due the obscure nature of the syntax. Hence my point about logical fallacy behind the self as! Self
construct. It doesn't smell right, intuitively.
self
and Self
will never have the same static type, the latter being a metatype. This line of code makes clear that you really are not correctly understanding what this code is doing, and I think this is the root of why you are finding this confusing. I urge you to review what @Nevin has explained, and to consult other sources, so that you find clarity.
There is no mumbo-jumbo here, and it is not an issue of syntax. If you are to understand how your code behaves in Swift with generics, dynamically dispatched methods, and runtime casts, you will need to master this distinction between static and dynamic types.