Formatted version: 
https://github.com/DevAndArtist/swift-evolution/blob/refactor_metatypes/proposals/0126-refactor-metatypes.md

Refactor Metatypes

Proposal: SE–0126
Authors: Adrian Zubarev, Anton Zhilin, Brent Royal-Gordon
Status: Revision
Review manager: Chris Lattner
Revision: 2
Previous Revisions: 1
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
Older swift-evolution threads: [1], [2], [3]

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>.

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):

Type<(Any) -> Void> is a subtype of Subtype<(Int) -> Void> etc.
Type<(Void) -> Int> is a subtype of Subtype<(Void) -> Any>
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
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>


-- 
Adrian Zubarev
Sent with Airmail
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to