[swift-evolution] [swift-evolution-announce] [Review] SE-0126: Refactor Metatypes, repurpose T.self and Mirror

Adrian Zubarev adrian.zubarev at devandartist.com
Thu Jul 21 09:59:52 CDT 2016


Hello Brent, thank you for your feedback on the review process of our proposal.

I think this proposal is a huge mess. I don’t understand why the split between Type and Metatype exists. I think Mirror seems half-baked; it ought to be omitted entirely until we can actually design a reflection system.
The reason why we took Mirror here, is because there can be metatypes that pretend to reflect T where the actual metatype could reflect U with relationship like U : T:

class Superclass {}
class Subclass : Superclass {}

let hidden: Any = Subclass()
let dynamicMetatype = hidden.dynamicType // Any.Type
dynamicMetatype as? Any.Type              //=> NOT nil
dynamicMetatype as? Superclass.Type       //=> NOT nil
dynamicMetatype as? Subclass.Type         //=> NOT nil
That is the reason why a standalone non-generic type was needed to solve the problem with the ‘current’ Swift.

And I can’t even tell if these are actual problems. It’s possible that the design is just fine, but the proposal explains it poorly. At minimum, the proposal should be rewritten and the type names reconsidered. I’m not a person who gets confused by metatypes, but I simply cannot make heads or tails of this proposal.

My general sense of this area is that the main problem is the dual meaning of T.Type. T.Type wants to simultaneously be the type of the type instance and the supertype of all subtype type instances. But this breaks down with protocols, because protocol existentials don’t conform to themselves. So we end up with T.Protocol, which gets confusingly renamed when it crosses generic boundaries.

I think the answer here is to split these two roles:

Metatype<T> is the type of the type instance. T.self is of type Metatype<T>. (Yes, we can call it T.metatype if we want.)
If we don’t go into the direction of Type<T> there is no need to rename the current T.self magic to T.metatype, that was only considered for the Type<T> model. .self will be removed one day anyways (hopefully).

Subtype-supertype relationships don’t translate directly to metaclasses; Metatype<Subclass> is not a subtype of Metatype<Superclass>.
Why is that so, see the example above?!

T.Subtype (or T.SubtypeMetatype? T.Conforming? this needs bikeshedding) is the supertype of all metatypes for T and its subtypes. T.Subtype is sort of like a protocol, in that there are no concrete instances of it, so it makes no sense to instantiate it. For classes, it only includes required (i.e. inherited) initializers.
Happily, I believe—though I may be wrong—that we can mostly resyntax to fix this immediate problem. Adding new capabilities, like extending the metatypes of specific types and adding universal members, can wait (or mostly wait) for another day.

(But in general, I would like to see those added directly on Metatype, and I would like extending Metatype<T> with instance members to be equivalent to extending T with static/class members. I’d also like to conform metatypes to protocols, to somehow define Metatype in the standard library as a relatively ordinary Swift type, and to have a pony.)
This is an interesting suggestion you mentioned there. But I think that would imply that every member you’d add on the generic Metatype<T> would be automatically not available on any Swift type:

// Bikeshedding example:

buildin Metatype<T> : Hashable {
    var hashValue: Int { .. }
}

struct New {
    // NOT available anymore - even if it's needed for a different purpose
    static hashValue: Int { .. }  
}
The issue can be solve if metatypes gain a bottleneck access to the type T, like Type<T>.metatype for example.

// Bikeshedding example:

buildin Metatype<T> : Hashable {
    var hashValue: Int { .. }
    var somethingToAccessT: T_XYZ { .. }
}

struct New {
    static hashValue: Int { .. } // available again
}
If we now compare this to our Type<T> model, we realize that the huge downside of Type<T> is that we cannot cast it around like we’d do with metatypes.

I’d appreciate the renaming of T.Type to Metatype<T>, but I believe this can’t be done without solving the issue with .Protocol first. Feel free to correct me, if I’m wrong here. :)

In SE–0101 rationale Chris said, that the core team considered to move size to types but dropped the idea because it would require T.self.

If the core team and the community strongly feels it would be better to introduce a new scoped buildin type (not necessarily named buildin), which would be only visible in stdlib, we might revision our proposal and shrink it down to the minimum breaking changes for Swift 3. Of course such a new scoped type can be introduces post Swift 3 to gain extensibility.

Steps I have in my mind are:

Rename T.Type to Metatype<T> today and resolve the issue with .Protocol somehow (I’m not a compiler expert).
I’d rename type(of:) from SE–0096 to metatype(of:) (or better dynamicMetatype<T>(of instance: T) -> Metatype<T>; not sure why ‘dynamic’ was dropped)
Drop the idea with Mirror, see below.
Come back post Swift 3 and talk about a new buildin extensible scoped type for metatypes with implicit inheritance ability like U : T, which merges the static and dynamic parts of Mirror and Type<T>.
// Future bikeshedding:

buildin Metatype<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {

    /// Creates an instance that reflects `T`.
    /// Example: `let type = T.self`
    public init()
     
    public static var somethingToAccessT: T_XYZ { get }
    public var somethingToAccessT: T_XYZ { get }

    public static var size: Int { get }
    public static var stride: Int { get }
    public static var alignment: Int { get }

    public var size: Int { get }
    public var stride: Int { get }
    public var alignment: Int { get }
     
    public var hashValue: Int { get }
    public var description: String { get }
    public var debugDescription: String { get }
}

func ==<T>(lhs: Metatype<T>, rhs: Metatype<T>) -> Bool {
    return lhs.hashValue == rhs.hashValue
}
If we can introduce this later, we might be able to drop the closed MemoryLayout enum then.

let metatype: Metatype<SomeType> = SomeType.self // or SomeType when `.self` is dropped
metatype.size // returns the size of `SomeType`
metatype.somethingToAccessT.staticMemember // from SomeType
metatype.somethingToAccessT.init // from SomeType

let hiddenMetatype: Metatype<Any> = metatype

(hiddenMetatype as? SomeType)?.somethingToAccessT

[Metatype<Any>: String] = [Int.self: "hello", Any.self: "swift"]  
The last ideas are my personal ideas which I do believe reflects some of your suggestions.

Anton might have a different point of view here.



-- 
Adrian Zubarev
Sent with Airmail
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160721/d108d93a/attachment.html>


More information about the swift-evolution mailing list