[Pitch] Refactor Metatypes


(Adrian Zubarev) #1

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


#2

Let me first say that this new draft does a great job of explaining the
current situation and the goals of the proposal. The revised “Motivation”
section in particular is very clearly written and introduces the concepts
effectively.

I found the previous versions to be highly confusing, and I did not
understand from them what was being proposed and why. After reading this
one, I feel that I finally comprehend the situation, both in terms of how
metatypes behave today and how the authors would like them to work.

This is still an area where I do not have much expertise, so I look forward
to hearing what better-versed people think of the overall approach as well
as the specific details.

Nevin

···

On Wed, Sep 28, 2016 at 6:18 AM, Adrian Zubarev via swift-evolution < swift-evolution@swift.org> wrote:

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

   - Proposal: SE–0126
   <http://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

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

   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>

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

protocol Foo {
  static func foo()
  func instanceMethodFoo()
}

protocol Boo : Foo {
  static func foo()
  static func boo()
  func instanceMethodFoo()
  func instanceMethodBoo()
}

class A : Foo {
  static func foo() { ... }
  func instanceMethodFoo() { ... }
}

class B : A, Boo {
  static func boo() { ... }
  func instanceMethodBoo() { ... }
}

/// Swift generates metatypes along the lines of:
///
/// Syntax: `meta protocol Subtype<T>` - only metatypes can conform to these meta protocols
/// Syntax: `final meta class Type<T>` - metatype
/// Note: `CapturedType` represents `Self` of `T` in `Subtype<T>`

// For Any:
meta protocol Subtype<Any> : meta class {
  var `self`: Self { get }
}

final meta class Type<Any> : Subtype<Any> {
  var `self`: Type<Any> { ... }
}

// For Foo:
meta protocol Subtype<Foo> : Subtype<Any> {
  var `self`: Self { get }
  func foo()
  func instanceMethodFoo(_ `self`: CapturedType) -> (Void) -> Void
}

final meta class Type<Foo> : Subtype<Any> {
  var `self`: Type<Foo> { ... }
  func instanceMethodFoo(_ `self`: Foo) -> (Void) -> Void { ... }
}

// For Boo:
meta protocol Subtype<Boo> : Subtype<Foo> {
  var `self`: Self { get }
  func boo()
  func instanceMethodBoo(_ `self`: CapturedType) -> (Void) -> Void
}

final meta class Type<Boo> : Subtype<Any> {
  var `self`: Type<Boo> { ... }
  func instanceMethodFoo(_ `self`: Boo) -> (Void) -> Void { ... }
  func instanceMethodBoo(_ `self`: Boo) -> (Void) -> Void { ... }
}

// For A:
meta protocol Subtype<A> : Subtype<Foo> {
  var `self`: Self { get }
  func foo()
  func instanceMethodFoo(_ `self`: CapturedType) -> (Void) -> Void
}

final meta class Type<A> : Subtype<A> {
  var `self`: Type<A> { ... }
  func foo() { ... }
  func instanceMethodFoo(_ `self`: A) -> (Void) -> Void { ... }
}

// For B:
meta protocol Subtype<B> : Subtype<A>, Subtype<Boo> {
  var `self`: Self
  func foo()
  func boo()
  func instanceMethodFoo(_ `self`: CapturedType) -> (Void) -> Void
  func instanceMethodBoo(_ `self`: CapturedType) -> (Void) -> Void
}

final meta class Type<B> : Subtype<B> {
  var `self`: Type<B> { ... }
  func foo() { ... }
  func boo() { ... }
  func instanceMethodFoo(_ `self`: B) -> (Void) -> Void { ... }
  func instanceMethodBoo(_ `self`: B) -> (Void) -> Void { ... }
}

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

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Colin Barrett) #3

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

Formatted version: 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

···

On Sep 28, 2016, at 6:18 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

--
Adrian Zubarev
Sent with Airmail

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Russ Bishop) #4

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.

Why would we not have type(of:) and subtype(of:)? Why would I want the Subtype<T> instead of the specific Type<T>?

What is the rationale for losing the meta type relationships by having Type<U> not be a subtype of Type<T>?

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

Some examples

I don’t see C defined in the example.

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:

To the extent possible I suggest that Swift 4+ stick to deprecations unless it presents a significant effort. I think a lot of people are a bit exhausted with huge syntax changes and the impression was that most source-breaking changes were being done in Swift 3 so we could do them once.

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

I think there is some potential for confusion with Subtype<T>, but I don’t want to bike shed it too much :slight_smile:

Overall looks really nice!

Russ

···

On Sep 28, 2016, at 3:18 AM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:


(Anton Zhilin) #5

Could someone tell if the proposal fits Phase 1? Seeing SE-0148—which
doesn't contain any ABI-breaking changes—being reviewed for Phase 1, I'm
not sure about anything.
The PR should either be labeled "out of scope of current release", or
merged. Or maybe it contains some fundamental flaws that even prevent it
from review?


(Anton Zhilin) #6

   -

   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?

#2 applies to types, and #5 applies to protocols. If T is a type, then
Type<T> is always a subtype of Subtype<T>. If T is a protocol, then Type<T>
is never a subtype of Subtype<T>.

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.

I honestly can’t imagine a use case for .Protocol currently. Maybe
enumerating protocols that exist in the program, just for the sake of it.

   -

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

Could you give a code example, where that would be an issue?

···

2016-09-29 3:31 GMT+03:00 Colin Barrett via swift-evolution < swift-evolution@swift.org>:


(Brent Royal-Gordon) #7

Why would we not have type(of:) and subtype(of:)? Why would I want the Subtype<T> instead of the specific Type<T>?

Let's turn this around. Suppose you write:

  let obj: NSObject = …
  let ty = type(of: obj)

What is `ty`? Well, it's a `Type<NSObject>`, and there's only one of those: `NSObject.self`. So there's only one possible instance that could be assigned to that variable.

This is true in general: If `type(of:)` returns `Type<T>`, then it can only have one possible return value. In other words, the return value of `type(of:)` would always be the *static* type of the variable, not its dynamic type. There may be some narrow cases where that'd be useful, but 99% of the time, you want `subtype(of:)` because you're trying to discover which of many dynamic subtypes of the static type you're actually dealing with. So most uses of `type(of:)` would probably be mistaken attempts to perform `subtype(of:)` instead.

What is the rationale for losing the meta type relationships by having Type<U> not be a subtype of Type<T>?

The relationships aren't lost; they're just expressed through `Subtype`, not `Type`.

Again, turn this around. `Subtype` is the normal thing that you'll want to use most of the time. `Type` is the weird thing whose existence is hard to explain. (One version of this proposal used `Type` for `Subtype` and `ExactType` for `Type` in order to imply that subtype is usually what you want, but some of the contributors weren't happy with that.)

So, `Type` is the weird thing. Why does it exist? Two reasons:

1. `Subtype<T>` only includes *inheritable* type members of `T`. `Type<T>` also includes *non-inheritable* members, particularly non-required initializers.

2. It allows precise type matches: `subty is Subtype<NSObject>` would match for any subtype of `NSObject`, whereas `subty is Type<NSObject>` would only match for `NSObject` itself.

···

On Sep 29, 2016, at 3:24 PM, Russ Bishop via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies


(Douglas Gregor) #8

SE-0148 fits in phase 1 because it’s in support of Phase 1 goals—specifically, it enables intended ABI-breaking changes in the standard library and is intended to be used as part of the String design.

AFAICT, the Refactoring Metatypes proposal is mostly independent. I view it as something that is important to look at in phase 2 because it is source-breaking, and if we’re going to do it, it should happen in Swift 4 rather than Swift 5.

  - Doug

···

On Jan 20, 2017, at 12:37 PM, Anton Zhilin via swift-evolution <swift-evolution@swift.org> wrote:

Could someone tell if the proposal fits Phase 1? Seeing SE-0148—which doesn't contain any ABI-breaking changes—being reviewed for Phase 1, I'm not sure about anything.
The PR should either be labeled "out of scope of current release", or merged. Or maybe it contains some fundamental flaws that even prevent it from review?


(Anton Zhilin) #9

I guess, now is the time? The response on this has been positive, both for
the new metatype syntax and separation of static and dynamic metatypes.

I’ve got one more feature in mind since then—a shorthand of AnyType ≡
AnyType<Any>.
Would this addition be worth extra syntax? Is it feasible from current
generics point of view? Anyway, it can be done in a separate proposal.


#10

From Brent’s explanation, it sounds to me like “Type” and “StaticType”

respectively would be more descriptive than “Subtype” and “Type” as
proposed.

Nevin

···

On Thu, Sep 29, 2016 at 10:29 PM, Brent Royal-Gordon via swift-evolution < swift-evolution@swift.org> wrote:

> On Sep 29, 2016, at 3:24 PM, Russ Bishop via swift-evolution < > swift-evolution@swift.org> wrote:
>
> Why would we not have type(of:) and subtype(of:)? Why would I want the
Subtype<T> instead of the specific Type<T>?

Let's turn this around. Suppose you write:

        let obj: NSObject = …
        let ty = type(of: obj)

What is `ty`? Well, it's a `Type<NSObject>`, and there's only one of
those: `NSObject.self`. So there's only one possible instance that could be
assigned to that variable.

This is true in general: If `type(of:)` returns `Type<T>`, then it can
only have one possible return value. In other words, the return value of
`type(of:)` would always be the *static* type of the variable, not its
dynamic type. There may be some narrow cases where that'd be useful, but
99% of the time, you want `subtype(of:)` because you're trying to discover
which of many dynamic subtypes of the static type you're actually dealing
with. So most uses of `type(of:)` would probably be mistaken attempts to
perform `subtype(of:)` instead.

> What is the rationale for losing the meta type relationships by having
Type<U> not be a subtype of Type<T>?

The relationships aren't lost; they're just expressed through `Subtype`,
not `Type`.

Again, turn this around. `Subtype` is the normal thing that you'll want to
use most of the time. `Type` is the weird thing whose existence is hard to
explain. (One version of this proposal used `Type` for `Subtype` and
`ExactType` for `Type` in order to imply that subtype is usually what you
want, but some of the contributors weren't happy with that.)

So, `Type` is the weird thing. Why does it exist? Two reasons:

1. `Subtype<T>` only includes *inheritable* type members of `T`. `Type<T>`
also includes *non-inheritable* members, particularly non-required
initializers.

2. It allows precise type matches: `subty is Subtype<NSObject>` would
match for any subtype of `NSObject`, whereas `subty is Type<NSObject>`
would only match for `NSObject` itself.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Russ Bishop) #11

I understand what you’re getting at.

This topic is confusing enough that I think it warrants being extremely clear about the naming. Type and Subtype feel like a class hierarchy and that’s not exactly what we are doing here. If Type<T> represents the static type of T then let’s just call it StaticType<T>. If the thing most people want to work with is the dynamic type Subtype<T> then let’s just call it that: DynamicType<T>.

Now this becomes really clear:

class A { }
class B: A { }

//clearly only ever represents A as a type
let metatype_1 = statictype(of: A())

//clearly might dynamically be A, B, or any subclass
let metatype_2 = dynamictype(of: A())

It also becomes trivially easy to explain because the name follows the explanation. Why can I only use required initializers on DynamicType<A>? Because we don’t know if the initializer is available; the dynamic type may differ at runtime. StaticType<A> knows exactly what is available on A because it is statically known at compile time.

Russ

···

On Sep 29, 2016, at 7:29 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Sep 29, 2016, at 3:24 PM, Russ Bishop via swift-evolution <swift-evolution@swift.org> wrote:

Why would we not have type(of:) and subtype(of:)? Why would I want the Subtype<T> instead of the specific Type<T>?

Let's turn this around. Suppose you write:

  let obj: NSObject = …
  let ty = type(of: obj)

What is `ty`? Well, it's a `Type<NSObject>`, and there's only one of those: `NSObject.self`. So there's only one possible instance that could be assigned to that variable.

This is true in general: If `type(of:)` returns `Type<T>`, then it can only have one possible return value. In other words, the return value of `type(of:)` would always be the *static* type of the variable, not its dynamic type. There may be some narrow cases where that'd be useful, but 99% of the time, you want `subtype(of:)` because you're trying to discover which of many dynamic subtypes of the static type you're actually dealing with. So most uses of `type(of:)` would probably be mistaken attempts to perform `subtype(of:)` instead.

What is the rationale for losing the meta type relationships by having Type<U> not be a subtype of Type<T>?

The relationships aren't lost; they're just expressed through `Subtype`, not `Type`.

Again, turn this around. `Subtype` is the normal thing that you'll want to use most of the time. `Type` is the weird thing whose existence is hard to explain. (One version of this proposal used `Type` for `Subtype` and `ExactType` for `Type` in order to imply that subtype is usually what you want, but some of the contributors weren't happy with that.)

So, `Type` is the weird thing. Why does it exist? Two reasons:

1. `Subtype<T>` only includes *inheritable* type members of `T`. `Type<T>` also includes *non-inheritable* members, particularly non-required initializers.

2. It allows precise type matches: `subty is Subtype<NSObject>` would match for any subtype of `NSObject`, whereas `subty is Type<NSObject>` would only match for `NSObject` itself.

--
Brent Royal-Gordon
Architechies


(Adrian Zubarev) #12

Personally I’d like a shortcut like AnyType, but it won’t work as a typealias, because it will create a circle reference between AnyType and AnyType<T>.

However, there is a small possibility that this type might be generalized like Any (now possibly AnyObject) directly into the language. That would probably allow us to use AnyType as a shortcut to AnyType<Any>.

I’m not sure if this should be part of our proposal or not. I’d like to have some more feedback from the core team on this one.

···

--
Adrian Zubarev
Sent with Airmail

Am 17. Februar 2017 um 14:24:14, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

I guess, now is the time? The response on this has been positive, both for the new metatype syntax and separation of static and dynamic metatypes.

I’ve got one more feature in mind since then—a shorthand of
AnyType ≡
AnyType<Any>.
Would this addition be worth extra syntax? Is it feasible from current generics point of view? Anyway, it can be done in a separate proposal.


(Xiaodi Wu) #13

I'm confused by this explanation.Today, `type(of:)` is the new
`.dynamicType`. Is this proposal suggesting a silent change so that it now
returns the static type? If so, why (particularly when you explain that
this is often *not* what you would want)?

I'm also somewhat puzzled about the proposed design. This proposal explains
that Subtype<T> should be a supertype of Type<T> and its subtypes. Why is a
supertype named Subtype?

···

On Thu, Sep 29, 2016 at 9:44 PM Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

From Brent’s explanation, it sounds to me like “Type” and “StaticType”
respectively would be more descriptive than “Subtype” and “Type” as
proposed.

Nevin

On Thu, Sep 29, 2016 at 10:29 PM, Brent Royal-Gordon via swift-evolution < > swift-evolution@swift.org> wrote:

> On Sep 29, 2016, at 3:24 PM, Russ Bishop via swift-evolution < >> swift-evolution@swift.org> wrote:
>
> Why would we not have type(of:) and subtype(of:)? Why would I want the
Subtype<T> instead of the specific Type<T>?

Let's turn this around. Suppose you write:

        let obj: NSObject = …
        let ty = type(of: obj)

What is `ty`? Well, it's a `Type<NSObject>`, and there's only one of
those: `NSObject.self`. So there's only one possible instance that could be
assigned to that variable.

This is true in general: If `type(of:)` returns `Type<T>`, then it can
only have one possible return value. In other words, the return value of
`type(of:)` would always be the *static* type of the variable, not its
dynamic type. There may be some narrow cases where that'd be useful, but
99% of the time, you want `subtype(of:)` because you're trying to discover
which of many dynamic subtypes of the static type you're actually dealing
with. So most uses of `type(of:)` would probably be mistaken attempts to
perform `subtype(of:)` instead.

> What is the rationale for losing the meta type relationships by having
Type<U> not be a subtype of Type<T>?

The relationships aren't lost; they're just expressed through `Subtype`,
not `Type`.

Again, turn this around. `Subtype` is the normal thing that you'll want
to use most of the time. `Type` is the weird thing whose existence is hard
to explain. (One version of this proposal used `Type` for `Subtype` and
`ExactType` for `Type` in order to imply that subtype is usually what you
want, but some of the contributors weren't happy with that.)

So, `Type` is the weird thing. Why does it exist? Two reasons:

1. `Subtype<T>` only includes *inheritable* type members of `T`.
`Type<T>` also includes *non-inheritable* members, particularly
non-required initializers.

2. It allows precise type matches: `subty is Subtype<NSObject>` would
match for any subtype of `NSObject`, whereas `subty is Type<NSObject>`
would only match for `NSObject` itself.

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Brent Royal-Gordon) #14

But that *isn't* clear about the relationship between DynamicType and StaticType—specifically, that there are no concrete DynamicTypes, and all DynamicType variables will contain StaticType instances.

In some drafts, I suggested pairs like `Type` and `SpecificType`:

  func type<T>(of: T) -> Type<T>
  func size<T>(of: SpecificType<T>) -> Int

(Incidentally, a third benefit of the redesign is that parameters can now be clear about whether they're actually sensitive to the specific dynamic type you pass or they're just being used to pin a generic parameter to a static type. For instance, when you call `UnsafeMutableRawPointer.initializeMemory(as:at:count:to:)`, it doesn't care what dynamic type you pass, but when you call `URLProtocol.registerClass(_:)`, it does. This would be represented—using the proposal's names—by `initializeMemory` taking a `Type<T>` and `URLProtocol` taking a `Subtype<URLProtocol>` or `Subtype<AnyObject>`.)

···

On Sep 30, 2016, at 10:25 PM, Russ Bishop <xenadu@gmail.com> wrote:
This topic is confusing enough that I think it warrants being extremely clear about the naming. Type and Subtype feel like a class hierarchy and that’s not exactly what we are doing here. If Type<T> represents the static type of T then let’s just call it StaticType<T>. If the thing most people want to work with is the dynamic type Subtype<T> then let’s just call it that: DynamicType<T>.

Now this becomes really clear:

class A { }
class B: A { }

//clearly only ever represents A as a type
let metatype_1 = statictype(of: A())

//clearly might dynamically be A, B, or any subclass
let metatype_2 = dynamictype(of: A())

It also becomes trivially easy to explain because the name follows the explanation. Why can I only use required initializers on DynamicType<A>? Because we don’t know if the initializer is available; the dynamic type may differ at runtime. StaticType<A> knows exactly what is available on A because it is statically known at compile time.

--
Brent Royal-Gordon
Architechies


(Adrian Zubarev) #15

Personally I’d prefer Metatype<T> and AnyMetatype<T> to get rid of the restriction where you mostly cannot create custom types called Type, which annoys me a lot. Sometimes Kind as a good workaround but there are times where Kind doesn’t fit. :confused:

···

--
Adrian Zubarev
Sent with Airmail

Am 18. Februar 2017 um 10:20:14, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

Yes,
Any,
AnyObject,
AnyType would look nice together. But I’d suggest to do it separately and for now, try to push the main proposal for review.

2017-02-17 19:09 GMT+03:00 Adrian Zubarev <adrian.zubarev@devandartist.com>:

Personally I’d like a shortcut like AnyType, but it won’t work as a typealias, because it will create a circle reference between AnyType and AnyType<T>.

However, there is a small possibility that this type might be generalized like Any (now possibly AnyObject) directly into the language. That would probably allow us to use AnyType as a shortcut to AnyType<Any>.

I’m not sure if this should be part of our proposal or not. I’d like to have some more feedback from the core team on this one.


(Brent Royal-Gordon) #16

I'm confused by this explanation.Today, `type(of:)` is the new `.dynamicType`. Is this proposal suggesting a silent change so that it now returns the static type? If so, why (particularly when you explain that this is often *not* what you would want)?

I'm short-handing the names to talk about the return values. In other words, I assume that, if we have both `type(of:)` and `subtype(of:)`, their signatures would be:

  func type<T>(of: T) -> Type<T>
  func subtype<T>(of: T) -> Subtype<T>

And I'm saying that, given these names, `type(of:)` is confusing and near-useless, whereas `subtype(of:)` is what you almost always want.

We *could*, of course, have a function called `type(of:)` which returned `Subtype<T>` and had the semantics I'm referring to as `subtype(of:)`. A name is just a name.

I'm also somewhat puzzled about the proposed design. This proposal explains that Subtype<T> should be a supertype of Type<T> and its subtypes. Why is a supertype named Subtype?

Because a type's name should describe the *instances*; that's why you don't put "Class" at the end of all of your class names. (It's also why we're proposing `Type<T>` instead of `Metatype<T>`.)

Every instance of `Subtype<T>` is the type instance for a subtype of `T`. For instance, in this hierarchy:

  NSObject
  NSResponder: NSObject
  NSView: NSResponder

`Type<NSResponder>` is a `Subtype<NSObject>`, but not a `Subtype<NSView>`.

Thus, this reads correctly:

  let aType: Subtype<NSResponder> = NSView.self

Whereas this does not:

  let aType: Supertype<NSResponder> = NSView.self

···

On Sep 29, 2016, at 8:14 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies


(Xiaodi Wu) #17

Great explanation.

Can you elaborate on your two reasons for the proposed Type<T>?

In particular, what do you mean by inheritable and non-inheritable members
of T? Where do these come into play? How is the current T.Type deficient in
this respect?

Regarding (2), it is already possible to do precise type matches using `==`
instead of `is` (see corelibs-foundation for extensive uses). What does the
proposal offer that goes beyond what we currently have?

···

On Thu, Sep 29, 2016 at 22:57 Brent Royal-Gordon <brent@architechies.com> wrote:

> On Sep 29, 2016, at 8:14 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
>
> I'm confused by this explanation.Today, `type(of:)` is the new
`.dynamicType`. Is this proposal suggesting a silent change so that it now
returns the static type? If so, why (particularly when you explain that
this is often *not* what you would want)?

I'm short-handing the names to talk about the return values. In other
words, I assume that, if we have both `type(of:)` and `subtype(of:)`, their
signatures would be:

        func type<T>(of: T) -> Type<T>
        func subtype<T>(of: T) -> Subtype<T>

And I'm saying that, given these names, `type(of:)` is confusing and
near-useless, whereas `subtype(of:)` is what you almost always want.

We *could*, of course, have a function called `type(of:)` which returned
`Subtype<T>` and had the semantics I'm referring to as `subtype(of:)`. A
name is just a name.

> I'm also somewhat puzzled about the proposed design. This proposal
explains that Subtype<T> should be a supertype of Type<T> and its subtypes.
Why is a supertype named Subtype?

Because a type's name should describe the *instances*; that's why you
don't put "Class" at the end of all of your class names. (It's also why
we're proposing `Type<T>` instead of `Metatype<T>`.)

Every instance of `Subtype<T>` is the type instance for a subtype of `T`.
For instance, in this hierarchy:

        NSObject
        NSResponder: NSObject
        NSView: NSResponder

`Type<NSResponder>` is a `Subtype<NSObject>`, but not a `Subtype<NSView>`.

Thus, this reads correctly:

        let aType: Subtype<NSResponder> = NSView.self

Whereas this does not:

        let aType: Supertype<NSResponder> = NSView.self

--
Brent Royal-Gordon
Architechies


(Goffredo Marocchi) #18

Hey Brent,

Sorry for being a bit dense about this, but...

   NSObject
   NSResponder: NSObject
   NSView: NSResponder

`Type<NSResponder>` is a `Subtype<NSObject>`, but not a `Subtype<NSView>`.

Thus, this reads correctly:

   let aType: Subtype<NSResponder> = NSView.self

Whereas this does not:

   let aType: Supertype<NSResponder> = NSView.self

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.

···

Sent from my iPhone

On 30 Sep 2016, at 04:57, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Sep 29, 2016, at 8:14 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

I'm confused by this explanation.Today, `type(of:)` is the new `.dynamicType`. Is this proposal suggesting a silent change so that it now returns the static type? If so, why (particularly when you explain that this is often *not* what you would want)?

I'm short-handing the names to talk about the return values. In other words, I assume that, if we have both `type(of:)` and `subtype(of:)`, their signatures would be:

   func type<T>(of: T) -> Type<T>
   func subtype<T>(of: T) -> Subtype<T>

And I'm saying that, given these names, `type(of:)` is confusing and near-useless, whereas `subtype(of:)` is what you almost always want.

We *could*, of course, have a function called `type(of:)` which returned `Subtype<T>` and had the semantics I'm referring to as `subtype(of:)`. A name is just a name.

I'm also somewhat puzzled about the proposed design. This proposal explains that Subtype<T> should be a supertype of Type<T> and its subtypes. Why is a supertype named Subtype?

Because a type's name should describe the *instances*; that's why you don't put "Class" at the end of all of your class names. (It's also why we're proposing `Type<T>` instead of `Metatype<T>`.)

Every instance of `Subtype<T>` is the type instance for a subtype of `T`. For instance, in this hierarchy:

   NSObject
   NSResponder: NSObject
   NSView: NSResponder

`Type<NSResponder>` is a `Subtype<NSObject>`, but not a `Subtype<NSView>`.

Thus, this reads correctly:

   let aType: Subtype<NSResponder> = NSView.self

Whereas this does not:

   let aType: Supertype<NSResponder> = NSView.self

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Goffredo Marocchi) #19

I think the names should be bikeshedded more still. From your examples they seem to make sense when I mentally add Of like TypeOf<> and SubTypeOf<>, but since you are not commenting on why that is wrong or right or misleading, or at least I have missed your reply on that, I am still a bit at loss. Maybe we need other words altogether?

···

Sent from my iPhone

On 2 Oct 2016, at 05:04, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

On Sep 30, 2016, at 10:25 PM, Russ Bishop <xenadu@gmail.com> wrote:
This topic is confusing enough that I think it warrants being extremely clear about the naming. Type and Subtype feel like a class hierarchy and that’s not exactly what we are doing here. If Type<T> represents the static type of T then let’s just call it StaticType<T>. If the thing most people want to work with is the dynamic type Subtype<T> then let’s just call it that: DynamicType<T>.

Now this becomes really clear:

class A { }
class B: A { }

//clearly only ever represents A as a type
let metatype_1 = statictype(of: A())

//clearly might dynamically be A, B, or any subclass
let metatype_2 = dynamictype(of: A())

It also becomes trivially easy to explain because the name follows the explanation. Why can I only use required initializers on DynamicType<A>? Because we don’t know if the initializer is available; the dynamic type may differ at runtime. StaticType<A> knows exactly what is available on A because it is statically known at compile time.

But that *isn't* clear about the relationship between DynamicType and StaticType—specifically, that there are no concrete DynamicTypes, and all DynamicType variables will contain StaticType instances.

In some drafts, I suggested pairs like `Type` and `SpecificType`:

   func type<T>(of: T) -> Type<T>
   func size<T>(of: SpecificType<T>) -> Int

(Incidentally, a third benefit of the redesign is that parameters can now be clear about whether they're actually sensitive to the specific dynamic type you pass or they're just being used to pin a generic parameter to a static type. For instance, when you call `UnsafeMutableRawPointer.initializeMemory(as:at:count:to:)`, it doesn't care what dynamic type you pass, but when you call `URLProtocol.registerClass(_:)`, it does. This would be represented—using the proposal's names—by `initializeMemory` taking a `Type<T>` and `URLProtocol` taking a `Subtype<URLProtocol>` or `Subtype<AnyObject>`.)

--
Brent Royal-Gordon
Architechies

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Anton Zhilin) #20

Type is a bad name for a public type: FooType is almost always a better
name. Libraries that describe “just types”, Swift types, can as well use
Metatype or Mirror or something.
For nested types, like Foo.Type, in the meaning of “type of Foo“, Type
can’t be used even now.
I’d give up on Type name for the greater future of metatypes.

Personally I’d prefer Metatype<T> and AnyMetatype<T> to get rid of the

restriction where you mostly cannot create custom types called Type,
which annoys me a lot. Sometimes Kind as a good workaround but there are
times where Kind doesn’t fit. :confused:

···

2017-02-18 16:28 GMT+03:00 Adrian Zubarev <adrian.zubarev@devandartist.com>: