Skip to content

Instantly share code, notes, and snippets.

@Anton3
Last active July 20, 2016 09:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Anton3/9931463695f1c3263333e18f04f9cd8e to your computer and use it in GitHub Desktop.
Save Anton3/9931463695f1c3263333e18f04f9cd8e to your computer and use it in GitHub Desktop.

Refactor metatypes

Introduction

  • Rename metatypes T.Type to Metatype<T>
  • Add Type<T> struct that wraps a static type
  • Make T.self syntax create Type<T>
  • Move static size, stride, alignment functions to Type<T>
  • Rename Mirror to DebugRepresentation and CustomReflectable to CustomDebugRepresentable
  • Add Mirror struct that is intended to replace metatypes for most use cases
  • Move dynamic size, stride, alignment functions to Mirror

Swift-evolution thread: Discussion thread topic for that proposal

Motivation

The following tasks require metatype-like types:

  1. Explicit specialization of functions and expressing specific static types
  2. Dynamic dispatch of static methods
  3. Representing any value as a tree, for debug purposes
  4. Retrieving and passing around information about dynamic types; reflection

Current state of things:

  • (1) is given to metatypes T.Type
    • Instance of metatype is usually ignored
    • For example, if you pass Derived.self as Base.self into function taking T.Type, it will work with Base
    • This raises concerns: are metatypes perfectly suited for that purpose?
  • (2) is also given to metatypes
    • Because they are used so often, it's tempting to add useful methods to them, but we can't, because it's not a struct
  • (3) is given to Mirror
    • Does its name reflect what it's intended to do?
    • Mirror.DisplayStyle contains optional and set as special cases, but does not contain function
    • Mirror collects all information possible at initialization, while for true reflection we want laziness
    • Mirror allows customization. For example, Array<T> is represented with a field for each of its elements. Do we want this for "true" reflection we want to add in the future?
  • (4) is given to both metatypes and Mirror
    • Metatypes are generic. But do we want genericity in reflection? No, we almost always want to cast to Any.Type
    • Metatypes are used for getting both static and dynamic sizes
    • In this context, distinction between generic parameter T and value of metatype instance is unclear
    • People are confused that Mirror is intended to be used for full-featured reflection, while it does not aim for that

Proposed solution

Metatype<T>

  • Rename T.Type to Metatype<T>
  • Remove T.self. Construction of metatypes will look like Type<T>.metatype, see below
  • Metatypes will be used only for dynamic dispatch of static methods, see example
protocol HasStatic   { static func staticMethod() -> String }
struct A : HasStatic { static func staticMethod() -> String { return "A" } }
struct B : HasStatic { static func staticMethod() -> String { return "B" } }

func callStatic(_ metatype: Metatype<HasStatic>) {
    let result = metatype.staticMethod()   
    print(result)
}

let a = Type<A>.metatype
let b = Type<B>.metatype
callStatic(a)  //=> A
callStatic(b)  //=> B

dynamicType will be renamed to dynamicMetatype:

func dynamicMetatype<T>(_ instance: T) -> Metatype<T>

Type<T>

T.self will be repurposed to return Type<T> that is declared as follows:

struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {

  // Type<T> is equivalent to (), but parametrized with type T
  init()
  
  static var size: Int { get }
  static var stride: Int { get }
  static var alignment: Int { get }
  static var metatype: Metatype<T> { get }
  
  // Equivalent to static versions
  var size: Int { get }
  var stride: Int { get }
  var alignment: Int { get }
  var metatype: Metatype<T> { get }
  
  // Implementation of protocols
  var hashValue: Int { get }
  var description: String { get }
  var debugDescription: String { get }
}

Size of Type<T> struct equals 0. It will be used for generic function specialization:

func performWithType(_ type: Type<T>)
performWithType(Float.self)

Mirror

Rename current Mirror to DebugRepresentation and CustomReflectable to CustomDebugRepresentable.

A completely different type with name Mirror will be introduced.

Mirror wraps metatypes and replaces usage of metatypes with as?, is, etc. Mirror contains dynamic versions of size, stride and alignment. Size of Mirror is 8 bytes. Mirror provides a starting point for adding fully functional reflection in the future.

struct Mirror : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
  
  /// Create mirror, reflecting type, which is reflected by a metatype
  init(_: Metatype<Any>)
  
  /// Create mirror, reflecting type T
  init<T>(_: Type<T>)
  
  /// Create mirror, reflecting dynamic type of a given instance
  init<T>(reflecting: T)
  
  var size: Int { get }
  var stride: Int { get }
  var alignment: Int { get }
  var metatype: Metatype<Any> { get }
  
  var hashValue: Int { get }
  var description: String { get }
  var debugDescription: String { get }
  
  /// Checks if type reflected by `self` is a subtype of type reflected by another `Mirror`.
  func `is`(_ mirror: Mirror) -> Bool
  
  /// Checks if type reflected by `self` is a subtype of `T`.
  func `is`<T>(_ type: Type<T>) -> Bool
  
  /// Checks if type reflected by `self` is a subtype of type reflected by a metatype.
  func `is`<T>(_ metatype: Metatype<T>) -> Bool
}

Summary of metatype-like types

Before

  • T.Type does 3 things:
    • Specialization of functions
    • Dynamic dispatch of static methods
    • Partial reflection using as, is and functions like sizeofValue
  • Mirror does 2 things:
    • It is primarily indented for use in debugging, like PlaygroundQuickLook
    • With less success, it can be used for reflection

After

  • Type<T> does specialization of functions
  • Mirror does reflection
  • Metatype<T> does dynamic dispatch of static methods
  • DebugRepresentation is used in debugging

Impact on existing code

Changes are source-breaking, automatic migration is possible.

  • T.TypeMetatype<T>
  • T.selfType<T>.metatype
  • MirrorDebugRepresentation
  • CustomReflectableCustomDebugRepresentable
    • customMirrorcustomDebugRepresentation
  • sizeof(T.self)Type<T>.size
  • sizeof(metatype)Mirror(metatype).size

Migrating metatype variables to use Type<T> and Mirror

Metatype<T> is a safe default for transition, but we want to discourage usage of metatypes. In some cases, we can provide fix-its to replace usage of Metatype<T> with Type<T> or Mirror.

To change type of a variable named type from Metatype<T> to Type<T>:

  1. Replace its type with Type<T>
  2. Use the migration patterns below
  3. If some use case does not match any of these, the variable cannot be migrated to type Type<T>

Migration patterns:

  • type = T.self.metatypetype = T.self
  • type = U.self.metatype where U != T → Automatic migration impossible
  • type = Type<T>.metatypetype = T.self
  • type = Type<U>.metatype where U != T → Automatic migration impossible
  • type = otherMetatype where otherMetatype: Metatype<T>type = T.self
  • type = otherMetatype where otherMetatype: Metatype<U>, U != T → Automatic migration impossible
  • type = mirror.metatype where mirror: Mirror → Automatic migration impossible
  • otherMetatype = type where otherMetatype: Metatype<U>otherMetatype = Type<T>.metatype
  • Mirror(type)Mirror(type)
  • type as otherMetatype where otherMetatype: Metatype<U>type.metatype as metatype<U>
  • type as? otherMetatype → Automatic migration impossible
  • type as! otherMetatype → Automatic migration impossible
  • type is otherMetatype → Automatic migration impossible

How to change type of a variable named type from Metatype<T> to Mirror:

  1. Replace its type with Mirror
  2. Use the migration patterns below
  3. If some use case does not match any of these, the variable cannot be migrated to type Mirror

Migration patterns:

  • type: Metatype<T>type: Mirror
  • type = U.self.metatypetype = Mirror(U.self)
  • type = Type<U>.metatypetype = Mirror(U.self)
  • type = otherMetatypetype = Mirror(otherMetatype)
  • type = mirror.metatype where mirror: Mirrortype = mirror
  • otherMetatype = typeotherMetatype = type.metatype
  • Mirror(type)type
  • type as otherMetatypetype.metatype as! otherMetatype
  • type as? otherMetatypetype.metatype as? otherMetatype
  • type as! otherMetatypetype.metatype as! otherMetatype
  • type is otherMetatypetype.is(otherMetatype)

We can also migrate metatype parameters of a function, where assignment means passing an argument to that function.

In two cases we will apply these transitions automatically:

  1. If a generic function takes parameter Metatype<T>, then we can try to replace Metatype<T> with Type<T>
  2. We can try to replace usage of Metatype<Any> (aka AnyMetatype) with Mirror

Standard library

Besides dynamicMetatype, the following global functions will be affected:

func transcode<Input, InputEncoding, OutputEncoding>(
    _: Input,
    from: Type<InputEncoding>,
    to: Type<OutputEncoding>,
    stoppingOnError: Bool,
    sendingOutputTo: @noescape (OutputEncoding.CodeUnit) -> Swift.Void
) where Input : IteratorProtocol, InputEncoding : UnicodeCodec,
        OutputEncoding : UnicodeCodec, InputEncoding.CodeUnit == Input.Element

func unsafeBitCast<T, U>(_: T, to: Type<U>)

func unsafeDowncast<T : AnyObject>(_: AnyObject, to: Type<T>)

Other standard library functions that took T.Type will most likely take Type<T>.

Future directions

Remove self

We can replace T.self notation with type literals T. Examples:

unsafeBitCast(1.0, to: Int)
let dynamicSize = Mirror(reflecting: something).size

Then we can add Type(_: Type<T>) initializer for disambiguation:

Int.self.size     // works with this proposal, but what if we drop `.self`?
Int.size          // will be error after dropping `.self`
Type<Int>().size  // works, but looks worse
Type(Int).size    // looks much better

When combined with this proposal, the result will be to eliminate all "magical" members that existed in the language: dynamicType, Type and self. There is also Self, but it acts like an associatedtype.

Extend reflection with Mirror

Reflection is one of stated goals for Swift 4. With this proposal, adding reflection becomes as simple as extending Mirror. For example, we can add the following computed property:

typealias FieldDescriptor = (name: String, type: Mirror, getter: (Any) -> Any, setter: (inout Any, Any) -> ())
var fields: [FieldDescriptor] { get }
@DevAndArtist
Copy link

Standard library section is not correct there are way more affected function (not speaking of computed properties - if there are any). I just checked some older release of swift in Xcode and searched for .Type in stdlib or only in stdlib/public:

Here are a few examples:

public func _isBridgedToObjectiveC<T>(_: T.Type) -> Bool
public func _isBridgedVerbatimToObjectiveC<T>(_: T.Type) -> Bool

public func == (t0: Any.Type?, t1: Any.Type?) -> Bool {
  return unsafeBitCast(t0, to: Int.self) == unsafeBitCast(t1, to: Int.self)
}

etc.

I don't want to check every of them, it's not our job if the proposal gets passed. We need to make it to sound plausible as possible.

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

How'd you express something like this on literals? _getMetatype(for: T) <-- Looks kinda odd because atm we cannot use type literals like that even if it's something internal. Besides that we call it Possible implementation which should be fine here.

func Builtin._getMetatype<T>(for staticType: Type<T>) -> Metatype<T>

Edit: But I'm fine with internal .metatype.

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

@DevAndArtist
I think, we should leave Metatype<T> in standard library by default, but try to transition to more appropriate Type<T> or Mirror over time.
New code should only consider using Metatype<T> in special cases.

@DevAndArtist
Copy link

I think this was a mistake: otherMetatype = type where otherMetatype: Metatype<U>otherMetatype = Type<T>.metatype

I believe it should be: otherMetatype: Metatype<T> = typeotherMetatype = Type<T>.metatype

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

@DevAndArtist
I believe, it should work for any Metatype<U>, not only Metatype<T>, because of implicit cast involved.
Plus, this should work for passing arguments to functions as well, then left part of assignment would be a parameter.

@DevAndArtist
Copy link

I've added the first block of your rules with some tweaks, I marked them, I don't understand all of them. Please compare them, because I could have messed something up.

I'll add the second block now.

@DevAndArtist
Copy link

Ahh now I think I understand what you mean. In a scenario where this was already safe we could safely migrate T to U. I'll fix this one.

@DevAndArtist
Copy link

Please compare the migration blocks. If I messed something up, please provide an explanation for that case so I can understand what I did wrong there.

@DevAndArtist
Copy link

DevAndArtist commented Jul 19, 2016

How about the name Refactor Metatypes, repurpose T.self and Mirror? I kinda have a bad feeling ppl will complain like "the proposal is misleading and does not what the title says", just like it happened with my last proposal.

We're almost there. A few things to check and to write, to review for mistakes and typos. Than we can open a proposal thread and push a PR. A proposal needs usually 7 days so the deadline is by tomorrow.

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

@DevAndArtist
This should read as "if all use cases of this variable are listed here and are not errors, then it's safe to perform all the replacements".

  • type: Metatype<T> → type: Type<T> -- OK
  • Mirror(type) → Mirror(type) -- I meant that such code pieces can be safely transitioned, and specifically, they should remain unchanged
  • type as Metatype<U> → Error → type.metatype as Metatype<U> -- By "error" I mean "quit transitioning; leave Metatype". So each case is either Error or result of transition
  • otherMetatype: Metatype<U> = type where type: Type<T>, T : U → otherMetatype = type.metatype -- Type of type is always Metatype<T> (and becomes Type<T> or Mirror); types of other variables used are declared separately in each case. And type cannot be Type<T> in left-hand side
  • Mirror(type) where type: Metatype<T>→ type where type: Mirror -- again, type is implied to be Metatype<T> at lhs and Type<T> or Mirror at rhs

Hopefully, that cleared up something.

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

@DevAndArtist
I agree with your naming choice.
It looks like only Known issues is still WIP. I'll try to write up something just in case, but you probably know current glitches better than I.

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

  • Search-and-replace typeTInstance with Type<T>()
  • Replace public var description: String { return "Mirror()" } with return "Mirror(\(self._metatype))"

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

  • It is primarily indented -> It is primarily intended

@DevAndArtist
Copy link

DevAndArtist commented Jul 19, 2016

I'm still a little confused by these migration rules. I think I'm done merging everything together. I tried my best with the format of the proposal. Could you review it and make some changes in a new gist. Possible typos, any (grammar) mistakes, fixing your migration rules. My English isn't the best, so I can't tell if I messed something up. 😅

After that we can move this proposal to a repo, open a new proposal thread, post there a formatted link and the whole proposal (I can do that, my mail client supports markdown) and finally PR this whole thing.

EDIT: Your last three suggestions are done.

@DevAndArtist
Copy link

I'll quickly add another glitch, forgot one.

@DevAndArtist
Copy link

Okay done, added two more glitches that I found yesterday. Feel free to review it in a new gist now and fix what I might have missed.

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

@DevAndArtist
I've modified migration part, you can safely copy markdown to the proposal.

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

Can you move Known issues section to right after Motivation? It should look like:

## Motivation
...
### Known issues of metatypes
...
## Proposed solution

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

Can you move the proposal to a fork of swift-evolution and start swift-evolution thread right now?
People will at least have a day to discuss.

@DevAndArtist
Copy link

I'll do, please review for possible typos or wrong language.

@DevAndArtist
Copy link

Here it is: GitHub Link

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

  • This proposal want -> This proposal wants
  • Intoduce a distinction -> Introduce a distinction
  • buildin -> builtin (multiple occurences)
  • by checking agains -> by checking against
  • metatype related informations -> metatype related information
  • checks type ralationshiop -> checks type ralationship
  • migration process, these can differ -> migration process; these can differ
  • Commas after Therefore, However

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

We can fix typos tomorrow.

@DevAndArtist
Copy link

Fixed them.

@DevAndArtist
Copy link

You can find the public proposal thread here: Link

Should I PR this with a note to merge it tomorrow?

@Anton3
Copy link
Author

Anton3 commented Jul 19, 2016

@DevAndArtist
I think yes, it's better to notify core team in advance.

@DevAndArtist
Copy link

We missed something:

The review of SE-0098: "Converting dynamicType from a property to an operator" ran from May 24…30, 2016. The proposal is accepted for Swift 3.

The feedback on the community on the proposal was very light, but there were significant concerns about its name. After discussion, the core team agrees that “x.dynamicType" should use the syntax of a global function as stated by the proposal, but would prefer for that function to be spelled "type(of: x)” to follow the naming guidelines.

I'll fix that to: metatype<T>(of instance: T) -> Metatype<T> and update the proposal wherever we used dynamicMetatype

@DevAndArtist
Copy link

@Anton3
Copy link
Author

Anton3 commented Jul 20, 2016

Nice! But it does not look like we are going to get any response from the community.

I tried to remember how some statically typed languages handle reflection. Only Java came to my mind (I know, not the best example).
In Java, they have Class type, which acts like proposed Mirror and has great reflection capabilities.

First fundamental difference: Swift needs a generic type (Type<T>) for implicit specialization of functions.
Second fundamental difference: Java has no metatypes. They use selectors to get required static method at runtime.

Following them, we could rename Mirror to Type and Type<T> to TypeLiteral<T>. I don't say we should do that, just something to consider.

Rust has little reflection capabilities, but it has non-generic TypeId type, which acts somewhat like Mirror, as well. This name also seems very clear to me.

@DevAndArtist
Copy link

TypeLiteral<T>.size sounds confusing to me. Literals are not types, therefore they don't have any size. Type<T>.size is more obvious (at least to me).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment