[swift-evolution] [Pitch] Refactor Metatypes

Colin Barrett colin at springsandstruts.com
Wed Sep 28 19:31:19 CDT 2016


First off, I agree with Nevin (up-thread) that this is a much clearer version of the previous proposal, well done.

> On Sep 28, 2016, at 6:18 AM, Adrian Zubarev via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Formatted version: https://github.com/DevAndArtist/swift-evolution/blob/refactor_metatypes/proposals/0126-refactor-metatypes.md <https://github.com/DevAndArtist/swift-evolution/blob/refactor_metatypes/proposals/0126-refactor-metatypes.md>
> Refactor Metatypes
> 
> Proposal: SE–0126 <x-msg://5/0126-refactor-metatypes-repurpose-t-dot-self-and-mirror.md>
> Authors: Adrian Zubarev <https://github.com/DevAndArtist>, Anton Zhilin <https://github.com/Anton3>, Brent Royal-Gordon <https://github.com/brentdax>
> Status: Revision
> Review manager: Chris Lattner <http://github.com/lattner>
> Revision: 2
> Previous Revisions: 1 <https://github.com/apple/swift-evolution/blob/83707b0879c83dcde778f8163f5768212736fdc2/proposals/0126-refactor-metatypes-repurpose-t-dot-self-and-mirror.md>
> Introduction
> 
> This proposal removes .Type and .Protocol in favor of two generic-style syntaxes and aligns global type(of:) function (SE–0096) to match the changes.
> 
> Swift-evolution thread (post Swift 3): 
> 
> [Pitch] Refactor Metatypes <applewebdata://A0AAC176-7F99-483F-BC96-BDA55910F1A4>
> Older swift-evolution threads: [1] <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160718/025115.html>, [2] <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160718/024772.html>, [3] <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160704/023818.html>
> Motivation
> 
> Every type T has an instance, accessible through T.self, which represents the type itself. Like all instances in Swift, this “type instance” itself has a type, which is referred to as its “metatype”. The metatype of T is written T.Type. The instance members of the metatype are the same as the static or class members of the type.
> 
> Metatypes have subtype relationships which reflect the types they represent. For instance, given these types:
> 
> protocol Proto {}
> class Base {}
> class Derived: Base, Proto {}
> Derived.Type is a subtype of both Base.Type and Proto.Type (and Any.Type). That means that Derived.self can be used anywhere a Derived.Type, Base.Type, Proto.Type, or Any.Type is called for.
> 
> Unfortunately, this simple picture is complicated by protocols. Proto.self is actually of type Proto.Protocol, not type Proto.Type. This is necessary because the protocol does not, and cannot, conform to itself; it requires conforming types to provide static members, but it doesn’t actually provide those members itself. Proto.Type still exists, but it is the supertype of all types conforming to the protocol.
> 
> Making this worse, a generic type always uses T.Type to refer to the type of T.self. So when Proto is bound to a generic parameter P, P.Type is the same as Proto.Protocol.
> 
> This shifting of types is complicated and confusing; we seek to clean up this area.
> 
> We also believe that, in the long term, the dot syntax will prevent us from implementing certain future enhancements that might be valuable:
> 
> Moving the implementation of metatypes at least partly into the standard library.
> Adding members available on all type instances for features like read-write reflection or memory layout information.
> Conforming metatypes to protocols like Hashable or CustomStringConvertible.
> Offering straightforward syntaxes for dynamic features like looking up types by name.
> Proposed solution
> 
> We abolish .Type and .Protocol in favor of two generic-style syntaxes:
> 
> Type<T> is the concrete type of T.self. A Type<T> only ever has one instance, T.self; even if T has a subtype U, Type<U> is not a subtype of Type<T>.
> 
> Subtype<T> is the supertype of all Types whose instances are subtypes of T, including T itself:
> 
> If T is a struct or enum, then Type<T> is the only subtype of Subtype<T>.
> 
> If T is a class, then Type<T> and the Types of all subclasses of T are subtypes of Subtype<T>.
> 
> If T is a protocol, then the Types of all concrete types conforming to T are subtypes of Subtype<T>. Type<T> is not itself a subtype of Subtype<T>, or of any Subtype other than Subtype<Any>.
> 
I’m having trouble reconciling this with rule #2 above, which states that “Subtype is the supertype of all Types whose instances are subtypes of T, including T itself.” Which one is wrong, or am I confused?

One thing I haven’t understood the motivation for exactly is what someone would be able to do with a Proto.self. Dynamic conformance checking? For a concrete T, having its .self seems useful for doing dynamic casts and such, but I don’t understand why for a Proto you need to have both. You did a good job of explaining why T.Protocol and T.Type are different, but not why both of them need to exist. So you could definitely do more to spell out the use-cases here.
> Structural types follow the subtype/supertype relationships of their constituent types. For instance:
> 
> Type<(NSString, NSString)> is a subtype of Subtype<(NSObject, NSObject)>
> 
> Metatypes of functions are a little bit more special (the subtyping relation on functions flips around for parameter types <https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)>):
> 
> Type<(Any) -> Void> is a subtype of Subtype<(Int) -> Void> etc.
> Type<(Void) -> Int> is a subtype of Subtype<(Void) -> Any>

Does this potentially expose contravariant type parameters, and is that an issue? (I’m trying to imagine a scenario where you could have an A on the left hand side of an arrow and have that leak out to other clients, but I haven’t had a chance to write much Swift 3 yet, unfortunately.)
> In this new notation, some of our existing standard library functions would have signatures like:
> 
> func unsafeBitCast<T, U>(_: T, to type: Type<U>) -> U
> func ==(t0: Subtype<Any>?, t1: Subtype<Any>?) -> Bool
> func type<T>(of: T) -> Subtype<T> // SE-0096
> That last example, type(of:), is rather interesting, because it is actually a magic syntax rather than a function. We propose to align this syntax with Type and Subtype by renaming it to Subtype(of:). We believe this is clearer about both the type and meaning of the operation.
> 
> let anInstance: NSObject = NSString()
> let aClass: Subtype<NSObject> = Subtype(of: anInstance)
> 
> print(aClass) // => NSString
> More details:
> 
> Every static or class member of T which can be called on all subtypes is an instance member of Subtype<T>. That includes:
> 
> Static/class properties and methods
> 
> Required initializers (as methods named init)
> 
> Unbound instance methods
> 
> The Type<T> of a concrete type T has all of the members required by Subtype<T>, plus non-required initializers.
> 
> The Type<T> of a protocol T includes only unbound instance methods of T.
> 
> If T conforms to P, then Subtype<T> is a subtype of Subtype<P>, even if T is a protocol.
> 
> The type of Subtype<T>.self is Type<Subtype<T>>.
> 
> The type of Type<T>.self is Type<Type<T>>, which is not a subtype of any type except Subtype<Type<T>>. There is an infinite regress of Type<...<Type<T>>>s.
> 
> Subtypes are abstract types similar to class-bound protocols; they, too, support identity operations. 
> 
> Types are concrete reference types which have identities just like objects do.
> 
> swift Int.self === Int.self // true Int.self === Any.self // false
> 
> 
> Visual metatype relationship example (not a valid Swift code)
> 
> 
> Some examples
> 
> // Types:
> protocol Foo {}
> protocol Boo : Foo {}
> class A : Foo {}
> class B : A, Boo {}
> struct S: Foo {}
> 
> // Metatypes:
> let a1: Type<A> = A.self           //=> Okay
> let p1: Type<Foo> = Foo.self       //=> Okay
> let p2: Type<Boo> = C.self         //=> Error -- `C` is not the same as `Foo`
> 
> let any_1: Subtype<Any> = A.self   //=> Okay
> let any_2: Subtype<Any> = Foo.self //=> Okay
> 
> let a_1: Subtype<A> = A.self       //=> Okay
> let p_1: Subtype<Foo> = A.self     //=> Okay
> let p_2: Subtype<Foo> = Foo.self   //=> Error -- `Type<Foo>` is not a subtype of `Subtype<Foo>`
> 
> // Generic functions:
> func dynamic<T>(subtype: Subtype<Any>, `is` _: Type<T>) -> Bool {
>   return type is Subtype<T>
> }
> 
> func dynamic<T>(subtype: Subtype<Any>, `as` _: Type<T>) -> Subtype<T>? {
>   return type as? Subtype<T>
> }
> 
> let s1: Type<S> = S.self
> 
> dynamic(subtype: s1, is: Foo.self)    //=> true
> dynamic(subtype: s1, as: Foo.self)    //=> an `Optional<Subtype<Foo>>`
> 
> 
> Future Directions
> 
> We could allow extensions on Type and perhaps on Subtype to add members or conform them to protocols. This could allow us to remove some standard library hacks, like the non-Equatable-related == operators for types.
> 
> It may be possible to implement parts of Type as a fairly ordinary final class, moving code from the runtime into the standard library.
> 
> We could offer a Subtype(ofType: Type<T>, named: String) pseudo-initializer which would allow type-safe access to classes by name.
> 
> We could offer other reflection and dynamic features on Type and Subtype.
> 
> We could move the MemoryLayout members into Type (presumably prefixed), removing the rather artificial MemoryLayout enum.
> 
> Along with other generics enhancements, there may be a use for a Subprotocol<T> syntax for any protocol requiring conformance to protocol T.
> 
> Impact on existing code
> 
> This is a source-breaking change that can be automated by a migrator. 
> 
> We suggest the following migration process; this can differ from the final migration process implemented by the core team if this proposal will be accepted:
> 
> Any.Type is migrated to Subtype<Any>.
> If T.Type is in function parameter, where T is a generic type parameter, then it’s migrated to Type<T>.
> Every T.Protocol will be replaced with Type<T>.
> Every T.Type in a dynamic cast will be replaced with Subtype<T>.
> If static members are called on a metatype instance, then this instance is migrated to Subtype<T>.
> Return types of functions are migrated to Subtype<T>.
> Variable declarations is migrated to Subtype<T>.
> Alternatives considered
> 
> Other names for Type and Subtype were considered:
> 
> Type: SpecificType, Metatype or ExactType.
> Subtype: Supertype, Base, BaseType, ExistentialType or TypeProtocol.
> Alternatively the pseudo initializer Subtype(of:) could remain as a global function:
> 
> public func subtype<T>(of instance: T) -> Subtype<T>

Thank you for the hard work you’ve put into this proposal, it’s come a long way!

-Colin

> 
> -- 
> Adrian Zubarev
> Sent with Airmail
> 
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160928/1fb629b0/attachment-0001.html>


More information about the swift-evolution mailing list