[swift-evolution] [Discussion] Seal `T.Type` into `Type<T>`

Adrian Zubarev adrian.zubarev at devandartist.com
Thu Jul 14 16:09:20 CDT 2016


Here is the formatted gist of the possible impementation: https://gist.github.com/DevAndArtist/a5744f21107812b2d4e6baee2c55e0bf



-- 
Adrian Zubarev
Sent with Airmail

Am 14. Juli 2016 um 23:02:52, Adrian Zubarev (adrian.zubarev at devandartist.com) schrieb:

I still can’t get my head around this.

Originally I wanted to seal T.Type completely but I realized that there are to many downsides, therefore I think it’s better to revise the way how we construct a metatype in Swift + how to solve the ambiguity in array/dictionary shorthand syntax + open the door for reflections + combining SE–0101 into Type<T>.

I don’t think we have to make the whole model of Type<T> that complicated that it contains different identifier for each type. This is already solved with metatypes.

The thing I believe you misinterpret with Type<T> being the same as T.Type is that if Type<T> would act like T.Type:

T.self.init == T.init
T.self.staticMember == T.staticMember
We won’t be able to extend Type<T> at all because otherwise all static members of Type<T> would be reserved and cannot be implemented in T, which is clearly not how it should work.

Type<T>.init != T.init
Type<T>.staticMember != T.staticMember   
Thats why I’m talking about the metatype property all the time.

Here is full bikeshedding implementation. I’ll use T.Metatype instead of T.Type for internal use only + T.metatype instead of T.self for internal use only + I’ll drop .self in public use and assume we can construct Type<T> from T.

/// `T.metatype` returns an instance of `T.Metatype` == `Metatype<T>`
/// `T.metatype` is only used internally and not visible in public
/// `T.Metatype` is visible in public but not allowed in declarations - use `Metatype<T>` instead
/// `T.self` is dropped in public
/// `T` returns an instance of `Type<T>` if its not part of a declaration
/// To instantiate `Metatype<T>` use `Type<T>.metatype` or `Type<T>().metatype`

internal func _sizeof<T>(_ metatype: Metatype<T>) -> Int {
    return Int(Builtin.sizeof(metatype))
}

internal func _strideof<T>(_ metatype: Metatype<T>) -> Int {
    return Int(Builtin.strideof_nonzero(metatype))
}

internal func _alignof<T>(_ metatype: Metatype<T>) -> Int {
    return Int(Builtin.alignof(metatype))
}

public func unsafeBitCast<T, U>(_: T, to: Type<U>) -> U { ... }

public typealias Metatype<T> = T.Metatype

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
      
    // Stored as `Any` to make `init?(casting:)` work as expected.
    // There is no need to contain different identifier like a Set!
    internal let _underlyingMetatype: Any
    internal let _uniqueIdentifier: Int
      
    public init() {
          
        self._underlyingMetatype = T.metatype
          
        // Same hash calculation like in `ObjectIdentifier`
        let rawPointerMetatype = unsafeBitCast(T.metatype, to: Builtin.RawPointer.metatype)
        self._uniqueIdentifier = Int(Builtin.ptrtoint_Word(rawPointerMetatype))
    }
      
    public init(_ copy: Type<T>) {

        self._underlyingMetatype = copy._underlyingMetatype
        self._uniqueIdentifier = copy._uniqueIdentifier
    }

    // Creates an instance referring to type, which is referred by `otherType`, if possible
    public init?<U>(casting otherType: Type<U>) {
          
        // check if we can up- or downcast ther metatype from `otherType` to `Metatype<T>`
        guard (otherType._underlyingMetatype as? Metatype<T>) != nil else {
            return nil
        }
          
        // create a new `Type<T>` but copy the metatype and the identifier from `otherType`
        self._underlyingMetatype = otherType._underlyingMetatype
        self._uniqueIdentifier = otherType._uniqueIdentifier
    }

    public func `is`<U>(_ otherType: Type<U>) -> Bool {
        return Type<U>(casting: self) != nil
    }
      
    public var metatype: Metatype<T> { return Type<T>.metatype }
    public static var metatype: Metatype<T> { return T.metatype }
      
    // do not construct full `Type<T>` - use lightweight static calculation instead
    public var size: Int { return Type<T>.size }
    public var stride: Int { return Type<T>.stride }
    public var alignment: Int { return Type<T>.alignment }
      
    public static var size: Int { return _sizeof(Type<T>.metatype) }
    public static var stride: Int { return _strideof(Type<T>.metatype) }
    public static var alignment: Int { return _alignof(Type<T>.metatype) }
      
    public var hashValue: Int { return self._uniqueIdentifier }
      
    public var description: String {
        return "Type<\(self.metatype)>"
    }
      
    public var debugDescription: String {
        return "<" + self.description
            + " metatype: \(self.metatype)"
            + " size: \(self.size)"
            + " stride: \(self.stride)"
            + " alignment: \(self.alignment)>"
    }
}

public func ==<T, U>(lhs: Type<T>, rhs: Type<U>) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

/// class A {}
/// class B: A {}
///
/// let anything: Any = B()
///
/// `Any.Type` or `Metatype<Any>`
/// let metatype = dynamicType(anything)
///
/// ((metatype as? Metatype<B>) != nil) == true
///

// This function can only extract the metatype from an instance.
// What we'll do to the metatype is up to us.
public func `dynamicType`<T>(_: T) -> Metatype<T> {
    return Type<T>.metatype
}
And proof of concept:

typealias Metatype<T> = T.Type

class A {}
class B: A {}
class C {}

let metatype: Any = B.self
((metatype as? Metatype<A>) != nil) == true
((metatype as? Metatype<B>) != nil) == true
((metatype as? Metatype<C>) != nil) == false


-- 
Adrian Zubarev
Sent with Airmail

Am 14. Juli 2016 um 21:01:24, Anton Zhilin (antonyzhilin at gmail.com) schrieb:

I didn't send the link to evolution, so here it is:
https://gist.github.com/Anton3/08a069a3b6f634bece7ad666922741d2

Response inline:

2016-07-14 20:52 GMT+03:00 Adrian Zubarev via swift-evolution <swift-evolution at swift.org>:
There is a small typo (SE–0090 is not accepted yet) - only in the first example:

func unsafeBitCast<T, U>(_: T, to: U.Type)
unsafeBitCast(10, to: Double)
       
// The second line should be
unsafeBitCast(10, to: Double.self)
I'll fix the examples.
Size and internal structure of Type will be the same as of T.Type
Do we really need this to be the same?
Yes, otherwise we cannot remove T.Type. If you want to store additional data in Type<T> and have a reason for that, then why not.
Values of Type store identifiers of U such that U: T.
Why would we want to store more than one unique identifier?
Another try at explaining my model of Type<T>. Warning: it will be a long read this time!

During compilation, each type is assigned a unique integer identifier. Let's suppose there are only three types used in the program: Any is 1, BaseClass is 2, DerivedClass is 3.

Values of type Type<Any> can contain one of identifiers 1, 2 or 3.
Values of type Type<BaseClass> can contain one of identifiers 2 or 3.
Values of type Type<DerivedClass> can only contain 3.

The same in terms of sets:

Type<Any> = { 1, 2, 3 }
Type<BaseClass> = { 2, 3 }
Type<DerivedClass> = { 3 }

In terms of set theory, type Type<T> contains identifiers of all types U that are subtypes of T.
If U1, ..., Uk are subtypes of T used in the program, then Type<T> = { T, U1, ..., Uk }

Example:

let x = Type<MyType>()
let y = Type<MyProtocol>(casting: x)
Type<MyType>.size      //=> 50
Type<MyProtocol>.size  //=> 40
x.size                 //=> 50
y.size                 //=> 50

Again, example with dynamicType. Let's suppose that T = BaseClass and DerivedClass: BaseClass.

func dynamicType(_ value: BaseClass) -> Type<BaseClass>

We can't know statically, which type information it returns.
Type<BaseClass> = { 2, 3 }
At runtime, we get to know if value is of BaseClass or of DerivedClass.

In my version, Type<T> should get all capabilities and all syntax of T.Type, therefore we should be able to drop the latter.

Again, main idea: rename T.Type to Type<T>, maintain its behaviour and tweak syntax.

Actually I thought for a while about the negative effect of fully removing metatypes from the language. Metatypes allow us to build neat looking execution branches like showed in SE–0101.
extension MemoryLayout<T> {
    init(_ : @autoclosure () -> T) {}
    public static func of(_ candidate : @autoclosure () -> T) -> MemoryLayout<T>.Type {
        return MemoryLayout.init(candidate).dynamicType
    }
}

// Value
let x: UInt8 = 5
MemoryLayout.of(x).size // 1
MemoryLayout.of(1).size // 8
MemoryLayout.of("hello").stride // 24
MemoryLayout.of(29.2).alignment // 8
I wouldn’t want to throw this away.

We won't lose literally anything by moving from T.Type to Type<T>.

of returns MemoryLayout<T>.Type, which currently doesn't have size property. Could you correct your example?
I played with the idea of keeping T.Type internally but disallow it in public declarations. Furthermore metatypes would still exist, but can only be instantiated through Type<T>.metatype or Type<T>().metatype.

To keep the neat designing feature but get rid of T.Type we could abuse generic typealiases here:

// T.Type would be only visible here but is disallowed in public declarations
// in favor of `Metatype<T>`
public typealias Metatype<T> = T.Type

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

    …
    public var metatype: Metatype<T> { return Type<T>.metatype }
       
    // Internally `.self` should be renamed to `.metatype` and return
    // a metatype instance    
    public static var metatype: Metatype<T> { return T.metatype }
    …
}
That way the sample from above won’t break from its designing point, but will require some refactoring:

extension MemoryLayout<T> {
    init(_ : @autoclosure () -> T) {}
    public static func of(_ candidate : @autoclosure () -> T) -> Metatype<MemoryLayout<T>> {
        return dynamicType(MemoryLayout.init(candidate)).metatype
    }
}
 If you wish, Type<T> in my version is rebranded metatype T.Type.
We should also mention that dynamic casts need some tweaking to work with Type<T>.
In the gist, I suggest to live without tweaking and replace dynamic casts with failable initializer of Type<T>. That will tweaking syntax of that casts, but reduce amount of magic.
And one more thing:
public var size: Int      { get }
public var stride: Int    { get }
public var alignment: Int { get }

public static var size: Int      { return Type<T>().size }
public static var stride: Int    { return Type<T>().stride }
public static var alignment: Int { return Type<T>().alignment }
Shouldn’t these work exactly the opposite way? If in the future Type<T> would be extended with reflection functionality and contain more stored properties, it would be lightweight to compute size etc. from static size without the need of instantiating the whole type.

See example above with sizes.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160714/8badb4f7/attachment.html>


More information about the swift-evolution mailing list