[swift-evolution] [Pitch] Refactor Metatypes

Adrian Zubarev adrian.zubarev at devandartist.com
Fri Sep 30 16:31:18 CDT 2016

I replied on your gist directly, but you can also read my reply below.

I though I made it crystal clear in my last post what the main problem is.


T.Type serves two jobs at once.

It’s a concrete metatype of T.
It’s an existential metatype of T where other metatypes where U is a subtype of T (U : T) are subtypes of this existential metatype.
Forget about protocols for a moment:

struct S { }  

let metatype_1: Any.Type = S.self // <~~ (concrete) metatype
// ~~~~~~~~~~~~ ^ existential metatype

let metatype_s: S.Type = S.self // <~~ (concrete) metatype
// ~~~~~~~~~~~~ ^ existential metatype

   The relationship looks here like this
   (concrete metatype) `T.Type : T.Type` (existential metatype)  
   OR for the given example: `S.Type : S.Type : Any.Type` (last one is again an existential metatype)
   This looks confusing right?

class B { }
class D : B { }

let metatype_b: B.Type = B.self // <~~ (concrete) metatype
// ~~~~~~~~~~~~ ^ existential metatype

metatype_b is D.Type // false

let metatype_d: D.Type = D.self // <~~ (concrete) metatype
// ~~~~~~~~~~~~ ^ existential metatype

let metatype_2: B.Type = metatype_d   // Totally fine
let metatype_3: Any.Type = metatype_2 // Okay

metatype_3 is D.Type // true

   (existential metatype) `B.Type : Any.Type` (existential metatype)
   (concrete metatype) `B.Type : B.Type` (existential metatype)
   (existential metatype) `D.Type : B.Type` (existential metatype)
   (concrete metatype) `D.Type : D.Type` (existential metatype)
It should be clear by now that there is this odd T.Type : T.Type relationship. We want to correct this behaviour + solve the problem that raises with protocols with one simple and single design.

Let’s see what happens with protocols:

protocol P { }  

let metatype_p: P.Type = P.self // Error, because the concrete metatype is not a subtype of the existential metatype of P

// Furthermore `P.self` is `P.Protocol`

let metatype_3: Any.Type = P.self // fine <~~ (concrete) metatype
// ~~~~~~~~~~~~ ^ existential metatype

   (concrete metatype) `P.Protocol : Any.Type` (existential metatype)
   (existential metatype) `P.Type : Any.Type` (existential metatype)
   At this time `P.Type : Any.Type` is an existential metatype that exists but it does not have any subtypes!

struct I : P { }

let metatype_i: I.Type = I.self // <~~ (concrete) metatype
// ~~~~~~~~~~~~ ^ existential metatype

let metatype_4: P.Type = metatype_i // fine
// ~~~~~~~~~~~~ ^ existential metatype

metatype_4 is I.Type // true

   (existential metatype) `P.Type : Any.Type` (existential metatype)
   (existential metatype) `I.Type : P.Type` (existential metatype)
   (concrete metatype) `I.Type : I.Type` (existential metatype)
There is a huge overlap in the current design. I hope this cleared your question here.

Side note: The following function isn’t possible to implement with the current T.Type design because in generic context a protocol will end up T.Protocol.

func dynamic<T>(subtype: Subtype<Any>, `is` _: Type<T>) -> Bool {
  return type is Subtype<T>
The proposed design however solves these problems and the relationship becomes clearer:

(existential metatype) `Subtype<B> : Subtype<Any>` (existential metatype)
(concrete metatype) `Type<B> : Subtype<B>` (existential metatype)
(existential metatype) `Subtype<D> : Subtype<B>` (existential metatype)
(concrete metatype) `Type<D> : Subtype<D>` (existential metatype)

(existential metatype) `Subtype<P> : Subtype<Any>` (existential metatype)
(concrete metatype) `Type<P> : Subtype<Any>` (existential metatype)

(existential metatype) `Subtype<P> : Subtype<Any>` (existential metatype)
(existential metatype) `Subtype<I> : Subtype<P>` (existential metatype)
(concrete metatype) `Type<I> : Subtype<I>` (existential metatype)
The only way to work with Subtype<T> is by using subtype(of:) function of by manually shadowing a concrete metatype Type<T>.

The only way to instantiate a concrete metatype is done with T.self.

Adrian Zubarev
Sent with Airmail

Am 30. September 2016 um 21:48:39, Xiaodi Wu (xiaodi.wu at gmail.com) schrieb:

Sorry, my question at least has nothing to do with bikeshedding. I'm confused about why the proposal feels it's necessary to have both Type and Subtype. I don't understand Brent's two reasons and was hoping for some elaboration. I've tried to clarify my question in a gist:


On Fri, Sep 30, 2016 at 2:09 PM, Adrian Zubarev via swift-evolution <swift-evolution at swift.org> wrote:
About the proposed names:

To be crystal clear we could use more descriptive names for our two types. Today T.Type is referred as *metatype* and serving two different purposes at once.

It’s a concrete type; we call it Type<T> or other suggested names looked like ExactType<T>, StaticType<T> etc.

T.Type is also the base type for all subtypes of T.

Protocols has one exception here.

1.1. The concrete type for protocols is not T.Type but T.Protocol.

2.1. T.Protocol has only one supertype, which is the existential (#2) Any.Type type.

Our proposal slices this behaviour into two different types, where you only can create a *concrete type* Type<T> with T.self or shadow a concrete type behind Subtype<U> with subtype(of:) function.

To be precise the correct names should be:

Metatype<T> for the concrete type (#1).
ExistentialMetatype<T> for the existential type (#2).
But we felt that we should adopt the existing name from T.Type and use the short form for the *concrete type* Type<T>.

Brent already showed in multiple examples but the question seems to come up over and over about the correct name of the current type(of:) function.

Imagine this scenario:

protocol P {}
struct A : P {}

let proto: P = A()
let any: Any = proto

// the old behaviour looked like this

// *concrete* `A.Type` is hidden behind the existential `Any.Type`
let anyMetatype: Any.Type = any.dynamicType   

anyMetatype is P.Type //=> true `P.Type` is the existential type here
anyMetatype is A.Type //=> true
let aMetatype = anyMetatype as! A.Type // Okay

// today `type(of:)` does the same trick

// After this proposal:
// subtype<T>(of instance: T) -> Subtype<T>

// The function will extract `Type<A>` for `any` but shadow it behind `Subtype<Any>`
let anyMetatype: `Subtype<Any>` = subtype(of: any)

// The correct relationship look like this:
// Subtype<P> : Subtype<Any>
// Subtype<A> : Subtype<P>
// Type<A> : Subtype<A>

anyMetatype is Subtype<P> //=> true
anyMetatype is Subtype<A> //=> true
anyMetatype is Type<A>    //=> true
anyMetatype is Type<P>    //=> false
anyMetatype is Type<Any>  //=> false
let aMetatype_1 = anyMetatype as! Subtype<A> // Okay
let aMetatype_2 = anyMetatype as! Type<A>    // Okay
subtype(of:) function extracts the *concrete type* from the given instance but shadows it behind the *existential type* equal to the type of the given instance.

subtype(of: T) returns a existential metatype instance Subtype<T> where in reality it’s a concrete metatype Type<U> with the relationship like U : T.

This is exact the same behaviour as the old .dynamicType had.

I hope that cleared some raising questions.

Adrian Zubarev
Sent with Airmail

Am 30. September 2016 um 09:00:53, Goffredo Marocchi via swift-evolution (swift-evolution at swift.org) schrieb:

Calling it SuperTypeOf<T> and SubTypeOf<T> would make it less confusing as that is how I read it in my mind in your last example.

swift-evolution mailing list
swift-evolution at swift.org

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160930/3a629861/attachment.html>

More information about the swift-evolution mailing list