metatype design question

a preambula,


i created a helper function:


func cast<T>(_ value: Any?, to: T.Type) -> T? {

return value != nil ? (value as! T) : nil

}


to use it like this:


let dog = cast(any, to: Dog.self)


the idea is that it is a "stricter" version of the "as?" operator:


let dog = any as? Dog // will return nil when a car is passed, i want a crash here in this case.

let dog = any != nil ? (any as! Dog) // so i want this: if it is not nil it must be a dog (*)

let dog = cast(any, to: Dog.self) // a bit shorter and less visually disturbing


(* note that the extra parens around "(any as! Dog)" are there to stop a warning).


now the question, probably to swift designers, but anyone's input is welcome:


this cast function is not as nice as the built-in "as?" operator. i can not pass "Dog" alone in this context, I have to put it as "Dog.self". is there some deep logic why it is so? i believe in this context "Dog" alone can not mean anything else (useful), what harm it would make to treat "Dog" here as if "Dog.self" was passed?

Accepted Reply

The difference between Dog and Dog.self is that "Dog" is just the name of the type, but "Dog.self" is a reference to the metatype object that represents the type. As it happens, it's possible for the compiler to distinguish between places where the name is required, and places where a metatype object is required, and it's a planned future enhancement to Swift to allow them both to be written Dog — but it's likely not a small change internally.


For now, you could try using any as! Dog? to "safely" cast an unknown optional to an optional Dog. It perhaps doesn't do exactly the same thing as your "cast" function, but it should be close enough. (For example, the two techniques might produce different results for a value of type Dog?? — that's a double optional, which is an edge case you probably don't care about.)

Replies

The difference between Dog and Dog.self is that "Dog" is just the name of the type, but "Dog.self" is a reference to the metatype object that represents the type. As it happens, it's possible for the compiler to distinguish between places where the name is required, and places where a metatype object is required, and it's a planned future enhancement to Swift to allow them both to be written Dog — but it's likely not a small change internally.


For now, you could try using any as! Dog? to "safely" cast an unknown optional to an optional Dog. It perhaps doesn't do exactly the same thing as your "cast" function, but it should be close enough. (For example, the two techniques might produce different results for a value of type Dog?? — that's a double optional, which is an edge case you probably don't care about.)

> any as! Dog?


wow, looks strange, but does the trick indeed!

thanks


out of curiousity, how do i create "Dog??" or "Dog???"

The most plausible scenario of intentionally creating a multilevel optional is from a dictionary with optional values:


let dict: [String: Dog?] = ... some values ...
let value = dict ["fido"]


Looking up a value in a dictionary adds a level of optionality, so "value" would be of type Dog?? FWIW, compare the meaning of the following two lines:


dict ["fifi"] = nil as Dog?
dict ["mimi"] = nil as Dog??


The first of those puts a nil value into the dictionary under key "fifi". The second deletes the value under key "mimi". 😉


More abstractly, you can add as many levels of optionality as you want, if you remember that Optional is an enum. You can write things like:


let weirdo = Optional.some(Optional.some(Optional.some(rex)))


"weirdo" is of type Dog??? 😉🙂😕

interesting.. and this:


dict["zizi"] = nil


acts here the same way as "dict["zizi"] = nil as Dog??" deleting the key.


are these two equivalent?

any as? Dog

any as? Dog?


and these two:

any as! Dog

any! as! Dog

Yes,


dict["zizi"] = nil


deletes the entry, because the type of "nil" is inferred to be "Dog??".


Your other pairs of example are more or less equivalent, but again I'm not sure they're exactly the same in edge cases. What's going on behaind the scenes is that the compiler has some ad-hoc rules about when it can ignore an extra level of optionality in the value being tested.


A related example is an optional try:


let result = try? someDog(named: "spot")


Previously, this would have added a level of optionality to the result. If "someDog" is defined to return a Dog?, the "try" would produce a Dog??. In Swift 5, this was changed to produce only a Dog?, eliminating one of the two levels of optionality.


The downside of this sort of thing is that it's harder to think about what's happening in terms of a simple rule. The upside is that the behavior is much more usable in typical cases.

> The downside of this sort of thing is that it's harder to think about what's happening in terms of a simple rule.


this is a bit worrisome. as your example with mimi vs fifi shows sometimes the number of ??? is important, and this fuzzy logic of compiler collapsing levels of optionality "with good intentions" might actually break something.