[Discussion] Seal `T.Type` into `Type<T>`


(Adrian Zubarev) #1

Hello Swift community, before Swift 3 drops I’d like to discuss if it is reasonable to consider to create a standalone type to drop/seal the T.Type magic?

However this change does not play well with the proposal of dropping the .self compiler magic.

Some bikeshedding:

public struct Type<T> : Hashable {
     
    // Seal the `.Type` magic into this standalone type.
    // Disallow `.Type` usage elsewhere
     
    public let sealed: T.Type
     
    public var hashValue: Int { /* implement somehow */ }
     
    public init() {
         
        // Or do it somehow different for the sake of dropping `.self` magic
        self.sealed = T.self
    }
}

public func ==<T>(lhs: Type<T>, rhs: Type<T>) -> Bool {
     
    return lhs.hashValue == rhs.hashValue
}
Downside of this approach is the accessibility of the .Type instance:

protocol Initializable {
    init()
}

func foo<T : Initializable>(type: Type<T>) -> T {
     
    // It would be nice if we could use `type.init()` instead, but than we
    // would need more compiler magic :confused:
    return type.sealed.init()
}
I couldn’t come up with a better name than sealed, if anyone has a better idea feel free to share your thoughts.

Remember this is a discussion and not (yet) a detailed proposal, where everyone can provide feedback and bikeshedding for a better design.

···

--
Adrian Zubarev
Sent with Airmail


(Adrian Zubarev) #2

Here are a few more thoughts on solving a few problems with dropped .self in mind:

Type<SomeType> cannot become Type<Type<SomeType>> to prevent infinite recursion.

Any other type without the short form for initializer SomeType(...) or explicit access to static members or initializer like SomeType.init(...) or SomeType.staticMember will be automatically become Type<SomeType> - if it’s not part of an array or dictionary shorthand syntax.

For array and dictionary shorthand syntax any type (except other nested array and dictionary shorthand types) does not follow the second rule from above (dropped .self in mind):

[SomeType]() equals Array<SomeType>()
[Type<SomeType>]() equals Array<Type<SomeType>>()
[SomeKeyType: SomeValueType]() equals Dictionary<SomeKeyType, SomeValueType>()
[Type<SomeKeyType>: Type<SomeValueType>]() equals Dictionary<Type<SomeKeyType>, Type<SomeValueType>>()
We also could extend Type<T> to provide more useful informations:

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
      
    // Anyone knows a better name?
    public let sealed: T.Type
      
    public var hashValue: Int { /* implement */ }
     
    // Inspired by SE-0101
    public var size: Int { /* implement */ }
    public var stride: Int { /* implement */ }
    public var alignment: Int { /* implement */ }
     
    public var description: String { /* implement */ }
     
    public var debugDescription: String { /* implement */ }
      
    public init() { /* implement */ }
}

···

--
Adrian Zubarev
Sent with Airmail

Am 8. Juli 2016 um 22:06:30, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Hello Swift community, before Swift 3 drops I’d like to discuss if it is reasonable to consider to create a standalone type to drop/seal the T.Type magic?

However this change does not play well with the proposal of dropping the .self compiler magic.

Some bikeshedding:

public struct Type<T> : Hashable {
      
    // Seal the `.Type` magic into this standalone type.
    // Disallow `.Type` usage elsewhere
      
    public let sealed: T.Type
      
    public var hashValue: Int { /* implement somehow */ }
      
    public init() {
          
        // Or do it somehow different for the sake of dropping `.self` magic
        self.sealed = T.self
    }
}

public func ==<T>(lhs: Type<T>, rhs: Type<T>) -> Bool {
      
    return lhs.hashValue == rhs.hashValue
}
Downside of this approach is the accessibility of the .Type instance:

protocol Initializable {
    init()
}

func foo<T : Initializable>(type: Type<T>) -> T {
      
    // It would be nice if we could use `type.init()` instead, but than we
    // would need more compiler magic :confused:
    return type.sealed.init()
}
I couldn’t come up with a better name than sealed, if anyone has a better idea feel free to share your thoughts.

Remember this is a discussion and not (yet) a detailed proposal, where everyone can provide feedback and bikeshedding for a better design.

--
Adrian Zubarev
Sent with Airmail


(Chris Lattner) #3

Hello Swift community, before Swift 3 drops I’d like to discuss if it is reasonable to consider to create a standalone type to drop/seal the T.Type magic?

Hi Adrian,

What’s the motivation for this change?

-Chris

···

On Jul 8, 2016, at 1:06 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

However this change does not play well with the proposal of dropping the .self compiler magic.

Some bikeshedding:

public struct Type<T> : Hashable {
     
    // Seal the `.Type` magic into this standalone type.
    // Disallow `.Type` usage elsewhere
     
    public let sealed: T.Type
     
    public var hashValue: Int { /* implement somehow */ }
     
    public init() {
         
        // Or do it somehow different for the sake of dropping `.self` magic
        self.sealed = T.self
    }
}

public func ==<T>(lhs: Type<T>, rhs: Type<T>) -> Bool {
     
    return lhs.hashValue == rhs.hashValue
}
Downside of this approach is the accessibility of the .Type instance:

protocol Initializable {
    init()
}

func foo<T : Initializable>(type: Type<T>) -> T {
     
    // It would be nice if we could use `type.init()` instead, but than we
    // would need more compiler magic :/
    return type.sealed.init()
}
I couldn’t come up with a better name than sealed, if anyone has a better idea feel free to share your thoughts.

Remember this is a discussion and not (yet) a detailed proposal, where everyone can provide feedback and bikeshedding for a better design.

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


(Adrian Zubarev) #4

Correcting the function from the very first post:

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

···

--
Adrian Zubarev
Sent with Airmail

Am 9. Juli 2016 um 11:55:00, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Here are a few more thoughts on solving a few problems with dropped .self in mind:

Type<SomeType> cannot become Type<Type<SomeType>> to prevent infinite recursion.

Any other type without the short form for initializer SomeType(...) or explicit access to static members or initializer like SomeType.init(...) or SomeType.staticMember will be automatically become Type<SomeType> - if it’s not part of an array or dictionary shorthand syntax.

For array and dictionary shorthand syntax any type (except other nested array and dictionary shorthand types) does not follow the second rule from above (dropped .self in mind):

[SomeType]() equals Array<SomeType>()
[Type<SomeType>]() equals Array<Type<SomeType>>()
[SomeKeyType: SomeValueType]() equals Dictionary<SomeKeyType, SomeValueType>()
[Type<SomeKeyType>: Type<SomeValueType>]() equals Dictionary<Type<SomeKeyType>, Type<SomeValueType>>()
We also could extend Type<T> to provide more useful informations:

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
       
    // Anyone knows a better name?
    public let sealed: T.Type
       
    public var hashValue: Int { /* implement */ }
      
    // Inspired by SE-0101
    public var size: Int { /* implement */ }
    public var stride: Int { /* implement */ }
    public var alignment: Int { /* implement */ }
      
    public var description: String { /* implement */ }
      
    public var debugDescription: String { /* implement */ }
       
    public init() { /* implement */ }
}

--
Adrian Zubarev
Sent with Airmail

Am 8. Juli 2016 um 22:06:30, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Hello Swift community, before Swift 3 drops I’d like to discuss if it is reasonable to consider to create a standalone type to drop/seal the T.Type magic?

However this change does not play well with the proposal of dropping the .self compiler magic.

Some bikeshedding:

public struct Type<T> : Hashable {
       
    // Seal the `.Type` magic into this standalone type.
    // Disallow `.Type` usage elsewhere
       
    public let sealed: T.Type
       
    public var hashValue: Int { /* implement somehow */ }
       
    public init() {
           
        // Or do it somehow different for the sake of dropping `.self` magic
        self.sealed = T.self
    }
}

public func ==<T>(lhs: Type<T>, rhs: Type<T>) -> Bool {
       
    return lhs.hashValue == rhs.hashValue
}
Downside of this approach is the accessibility of the .Type instance:

protocol Initializable {
    init()
}

func foo<T : Initializable>(type: Type<T>) -> T {
       
    // It would be nice if we could use `type.init()` instead, but than we
    // would need more compiler magic :confused:
    return type.sealed.init()
}
I couldn’t come up with a better name than sealed, if anyone has a better idea feel free to share your thoughts.

Remember this is a discussion and not (yet) a detailed proposal, where everyone can provide feedback and bikeshedding for a better design.

--
Adrian Zubarev
Sent with Airmail


(Adrian Zubarev) #5

Hello Chris,

my main concern for this change is the lack of extensibility of metatypes. We can access the metatype through the .self postfix, which potentially will be removed in the future (only the postfix). Through an instance of such a metatype we can then access the static members and all initializer for our type.

SomeType.staticMember equals metatypeInstance.staticMemeber
SomeType.init equals metatypeInstance.init
It is also possible to extract some more information from the metatypes with the help of Buildin wrapper functions like sizeof or strideof. And the last but not the least we can use metatypes in type checking scenarios.

Last weak I asked the community if there is a need for all metatypes conform to the Hashable protocol, which would be really handy. The overall response was positive, even if only a handful of people responded.

The whole idea of metatypes conforming to Hashable kept me thinking how this might be solved in the future. The main problem is that metatypes provide direct access to initializer and static members of the type and a conformance to Hashable seems to be impossible without some compiler magic or a hidden hashValue property. The main confusion here might be that metatypes can be stored (at least while runtime) inside variables and pretend to be instances of a T.Type type.

That said, I’d like to consider of sealing the direct access to a metatype into a real type for the sake of extensibility and clarity.

I’m aware that my bikeshedding introduced more boilerplate in terms of accessing the initializer and static members through a bottleneck property called sealed. Furthermore there is one more character to type if we’d go with Type<T> instead of T.Type.

This is why this is a discussion before making any further decisions. :slight_smile:

PS: As I showed in my last code example a wrapper type Type<T> can also provide a few more information about the metatype.

Type<T>.size
Type<T>.stride
Type<T>.alignment
Furthermore sealing T.Type into Type<T> does solve the problem with the array/dictionary shorthand syntax.

Only when one would store/pass a type (name) we’d implicitly convert SomeType to Type<SomeType>.

Declarations do not follow this implicit conversion:

func foo(_: SomeType) is not equivalent to func foo(_: Type<SomeType>)
Array<SomeType>() == [SomeType]() but is not equivalent to [Type<SomeType>]() == Array<Type<SomeType>>()
Here is again my bikeshedding:

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
     
    // `metatype` is a better name than `sealed`
    // The tradeoff is this bottleneck property

    public let metatype: T.Type
      
    public let hashValue: Int
      
    public static var size: Int { get }
    public static var stride: Int { get }
    public static var alignment: Int { get }
      
    public var size: Int { get }
    public var stride: Int { get }
    public var alignment: Int { get }
      
    public var description: String { get }
    public var debugDescription: String { get }
}

public func ==<T, U>(lhs: Type<T>, rhs: Type<U>) -> Bool

···

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 00:18:02, Chris Lattner (clattner@apple.com) schrieb:

Hi Adrian,

What’s the motivation for this change?

-Chris


(Adrian Zubarev) #6

Currently I could implement the whole type like this:

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
     
    // Anyone knows a better name?
    public let sealed: T.Type = T.self
     
    public let hashValue: Int = ObjectIdentifier(T.self).hashValue
     
    // Inspired by SE-0101
    public static var size: Int { return sizeof(T.self) }
    public static var stride: Int { return strideof(T.self) }
    public static var alignment: Int { return alignof(T.self) }
     
    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 var description: String { return "Type<\(self.sealed)>" }
     
    public var debugDescription: String {
        return "<" + self.description + " 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
}
I also added static computed properties to access the the memory layout without any initialization.

Type<Int>.size reads really great to me.

I’m curious about the feedback from the Swift community. :slight_smile:

PS: All my replies are markdown formatted. If anyone can’t read them, here is a formatted gist: https://gist.github.com/DevAndArtist/c35284ac12806c13eff302d1cf347ac8

···

--
Adrian Zubarev
Sent with Airmail

Am 9. Juli 2016 um 12:03:39, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Correcting the function from the very first post:

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

--
Adrian Zubarev
Sent with Airmail

Am 9. Juli 2016 um 11:55:00, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Here are a few more thoughts on solving a few problems with dropped .self in mind:

Type<SomeType> cannot become Type<Type<SomeType>> to prevent infinite recursion.

Any other type without the short form for initializer SomeType(...) or explicit access to static members or initializer like SomeType.init(...) or SomeType.staticMember will be automatically become Type<SomeType> - if it’s not part of an array or dictionary shorthand syntax.

For array and dictionary shorthand syntax any type (except other nested array and dictionary shorthand types) does not follow the second rule from above (dropped .self in mind):

[SomeType]() equals Array<SomeType>()
[Type<SomeType>]() equals Array<Type<SomeType>>()
[SomeKeyType: SomeValueType]() equals Dictionary<SomeKeyType, SomeValueType>()
[Type<SomeKeyType>: Type<SomeValueType>]() equals Dictionary<Type<SomeKeyType>, Type<SomeValueType>>()
We also could extend Type<T> to provide more useful informations:

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
        
    // Anyone knows a better name?
    public let sealed: T.Type
        
    public var hashValue: Int { /* implement */ }
       
    // Inspired by SE-0101
    public var size: Int { /* implement */ }
    public var stride: Int { /* implement */ }
    public var alignment: Int { /* implement */ }
       
    public var description: String { /* implement */ }
       
    public var debugDescription: String { /* implement */ }
        
    public init() { /* implement */ }
}

--
Adrian Zubarev
Sent with Airmail

Am 8. Juli 2016 um 22:06:30, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Hello Swift community, before Swift 3 drops I’d like to discuss if it is reasonable to consider to create a standalone type to drop/seal the T.Type magic?

However this change does not play well with the proposal of dropping the .self compiler magic.

Some bikeshedding:

public struct Type<T> : Hashable {
        
    // Seal the `.Type` magic into this standalone type.
    // Disallow `.Type` usage elsewhere
        
    public let sealed: T.Type
        
    public var hashValue: Int { /* implement somehow */ }
        
    public init() {
            
        // Or do it somehow different for the sake of dropping `.self` magic
        self.sealed = T.self
    }
}

public func ==<T>(lhs: Type<T>, rhs: Type<T>) -> Bool {
        
    return lhs.hashValue == rhs.hashValue
}
Downside of this approach is the accessibility of the .Type instance:

protocol Initializable {
    init()
}

func foo<T : Initializable>(type: Type<T>) -> T {
        
    // It would be nice if we could use `type.init()` instead, but than we
    // would need more compiler magic :confused:
    return type.sealed.init()
}
I couldn’t come up with a better name than sealed, if anyone has a better idea feel free to share your thoughts.

Remember this is a discussion and not (yet) a detailed proposal, where everyone can provide feedback and bikeshedding for a better design.

--
Adrian Zubarev
Sent with Airmail


(L Mihalkovic) #7

It looks like this is touching on a small subset of the not-yet-designed reflection API (still tabled for 4.0?). I am interested to see what balance it strikes between declarations (eg. being able to reflect extensions declarations), types (eg reflecting type conformance), and operations (xxx.newInstance(), xxx.setValue()). The mirror api shows a general direction, maybe there is a way to squeeze a tiny subset in 3.0 (just like was done with P&Q that will remain a degenerate case of the upcoming more general syntax). Maybe just enough for people not to have to use ObjectIdentifier(:Any.Type).

Regards
(From mobile)

···

On Jul 10, 2016, at 2:22 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello Chris,

my main concern for this change is the lack of extensibility of metatypes. We can access the metatype through the .self postfix, which potentially will be removed in the future (only the postfix). Through an instance of such a metatype we can then access the static members and all initializer for our type.

SomeType.staticMember equals metatypeInstance.staticMemeber
SomeType.init equals metatypeInstance.init
It is also possible to extract some more information from the metatypes with the help of Buildin wrapper functions like sizeof or strideof. And the last but not the least we can use metatypes in type checking scenarios.

Last weak I asked the community if there is a need for all metatypes conform to the Hashable protocol, which would be really handy. The overall response was positive, even if only a handful of people responded.

The whole idea of metatypes conforming to Hashable kept me thinking how this might be solved in the future. The main problem is that metatypes provide direct access to initializer and static members of the type and a conformance to Hashable seems to be impossible without some compiler magic or a hidden hashValue property. The main confusion here might be that metatypes can be stored (at least while runtime) inside variables and pretend to be instances of a T.Type type.

That said, I’d like to consider of sealing the direct access to a metatype into a real type for the sake of extensibility and clarity.

I’m aware that my bikeshedding introduced more boilerplate in terms of accessing the initializer and static members through a bottleneck property called sealed. Furthermore there is one more character to type if we’d go with Type<T> instead of T.Type.

This is why this is a discussion before making any further decisions. :slight_smile:

PS: As I showed in my last code example a wrapper type Type<T> can also provide a few more information about the metatype.

Type<T>.size
Type<T>.stride
Type<T>.alignment
Furthermore sealing T.Type into Type<T> does solve the problem with the array/dictionary shorthand syntax.

Only when one would store/pass a type (name) we’d implicitly convert SomeType to Type<SomeType>.

Declarations do not follow this implicit conversion:

func foo(_: SomeType) is not equivalent to func foo(_: Type<SomeType>)
Array<SomeType>() == [SomeType]() but is not equivalent to [Type<SomeType>]() == Array<Type<SomeType>>()
Here is again my bikeshedding:

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
     
    // `metatype` is a better name than `sealed`
    // The tradeoff is this bottleneck property

    public let metatype: T.Type
      
    public let hashValue: Int
      
    public static var size: Int { get }
    public static var stride: Int { get }
    public static var alignment: Int { get }
      
    public var size: Int { get }
    public var stride: Int { get }
    public var alignment: Int { get }
      
    public var description: String { get }
    public var debugDescription: String { get }
}

public func ==<T, U>(lhs: Type<T>, rhs: Type<U>) -> Bool

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 00:18:02, Chris Lattner (clattner@apple.com) schrieb:

Hi Adrian,

What’s the motivation for this change?

-Chris

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


(Adrian Zubarev) #8

Can you point me to some reading? I’m curious what exactly you’re talking about!?

···

I found a negative side effect of sealing T.Type. The problem appears lies in the dynamic is cast.

Currently we could do something like this:

func isInt<T>(metatype: T.Type) -> Bool {
     
    return metatype is Int.Type
}
There is no way to express the same type check with Type<T> except this:

func isInt<T>(type: Type<T>) -> Bool {
     
    return type == Type<Int>()
     
    // since this check is something where we store/pass a type (name)
    // we could implicitly convert `Int` into an instance of `Type<Int>`
    //
    // The result would look like this:
     
    return type == Int // which is logically equivalent to `metatype is Int.Type`
}

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 16:24:50, L. Mihalkovic (laurent.mihalkovic@gmail.com) schrieb:

It looks like this is touching on a small subset of the not-yet-designed reflection API (still tabled for 4.0?). I am interested to see what balance it strikes between declarations (eg. being able to reflect extensions declarations), types (eg reflecting type conformance), and operations (xxx.newInstance(), xxx.setValue()). The mirror api shows a general direction, maybe there is a way to squeeze a tiny subset in 3.0 (just like was done with P&Q that will remain a degenerate case of the upcoming more general syntax). Maybe just enough for people not to have to use ObjectIdentifier(:Any.Type).

Regards
(From mobile)

On Jul 10, 2016, at 2:22 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello Chris,

my main concern for this change is the lack of extensibility of metatypes. We can access the metatype through the .self postfix, which potentially will be removed in the future (only the postfix). Through an instance of such a metatype we can then access the static members and all initializer for our type.

SomeType.staticMember equals metatypeInstance.staticMemeber
SomeType.init equals metatypeInstance.init
It is also possible to extract some more information from the metatypes with the help of Buildin wrapper functions like sizeof or strideof. And the last but not the least we can use metatypes in type checking scenarios.

Last weak I asked the community if there is a need for all metatypes conform to the Hashable protocol, which would be really handy. The overall response was positive, even if only a handful of people responded.

The whole idea of metatypes conforming to Hashable kept me thinking how this might be solved in the future. The main problem is that metatypes provide direct access to initializer and static members of the type and a conformance to Hashable seems to be impossible without some compiler magic or a hidden hashValue property. The main confusion here might be that metatypes can be stored (at least while runtime) inside variables and pretend to be instances of a T.Type type.

That said, I’d like to consider of sealing the direct access to a metatype into a real type for the sake of extensibility and clarity.

I’m aware that my bikeshedding introduced more boilerplate in terms of accessing the initializer and static members through a bottleneck property called sealed. Furthermore there is one more character to type if we’d go with Type<T> instead of T.Type.

This is why this is a discussion before making any further decisions. :slight_smile:

PS: As I showed in my last code example a wrapper type Type<T> can also provide a few more information about the metatype.

Type<T>.size
Type<T>.stride
Type<T>.alignment
Furthermore sealing T.Type into Type<T> does solve the problem with the array/dictionary shorthand syntax.

Only when one would store/pass a type (name) we’d implicitly convert SomeType to Type<SomeType>.

Declarations do not follow this implicit conversion:

func foo(_: SomeType) is not equivalent to func foo(_: Type<SomeType>)
Array<SomeType>() == [SomeType]() but is not equivalent to [Type<SomeType>]() == Array<Type<SomeType>>()
Here is again my bikeshedding:

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
      
    // `metatype` is a better name than `sealed`
    // The tradeoff is this bottleneck property

    public let metatype: T.Type
       
    public let hashValue: Int
       
    public static var size: Int { get }
    public static var stride: Int { get }
    public static var alignment: Int { get }
       
    public var size: Int { get }
    public var stride: Int { get }
    public var alignment: Int { get }
       
    public var description: String { get }
    public var debugDescription: String { get }
}

public func ==<T, U>(lhs: Type<T>, rhs: Type<U>) -> Bool

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 00:18:02, Chris Lattner (clattner@apple.com) schrieb:

Hi Adrian,

What’s the motivation for this change?

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


(Adrian Zubarev) #9

Just to be crystal clear, here is my vision.

Currently Swift is heading in this direction:

SomeType.staticMember == metatypeInstance.staticMember == SomeType.self.staticMember
SomeType.init == metatypeInstance.init == SomeType.self.init

SomeType.self -> SomeType.Type (metatype)

SomeType -> SomeType.Type (removed .self)

SomeType.self is SomeType.Type == true

func foo<T>(metatype: T.Type) -> T {
     
    // lets pretend T conforms to an initializable protocol
    return metatype.init()
}
With some tweaking and a little tradeoff for Type<T> we’d get this model:

SomeType.staticMember == Type<SomeType>.metatype.staticMember
SomeType.init == Type<SomeType>.metatype.init

SomeType -> Type<SomeType>() (initialized instance; removed .self)

SomeType == Type<SomeType>() == true

SomeType is Type<SomeType>() == true

func foo<T>(type: Type<T>) -> T {
     
    print(type.size)
    print(type.debugDescription)
    print(type.hashValue)
     
    // lets pretend T conforms to an initializable protocol
    return type.metatype.init()
}

···

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 17:28:35, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Can you point me to some reading? I’m curious what exactly you’re talking about!?

I found a negative side effect of sealing T.Type. The problem appears lies in the dynamic is cast.

Currently we could do something like this:

func isInt<T>(metatype: T.Type) -> Bool {
      
    return metatype is Int.Type
}
There is no way to express the same type check with Type<T> except this:

func isInt<T>(type: Type<T>) -> Bool {
      
    return type == Type<Int>()
      
    // since this check is something where we store/pass a type (name)
    // we could implicitly convert `Int` into an instance of `Type<Int>`
    //
    // The result would look like this:
      
    return type == Int // which is logically equivalent to `metatype is Int.Type`
}

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 16:24:50, L. Mihalkovic (laurent.mihalkovic@gmail.com) schrieb:

It looks like this is touching on a small subset of the not-yet-designed reflection API (still tabled for 4.0?). I am interested to see what balance it strikes between declarations (eg. being able to reflect extensions declarations), types (eg reflecting type conformance), and operations (xxx.newInstance(), xxx.setValue()). The mirror api shows a general direction, maybe there is a way to squeeze a tiny subset in 3.0 (just like was done with P&Q that will remain a degenerate case of the upcoming more general syntax). Maybe just enough for people not to have to use ObjectIdentifier(:Any.Type).

Regards
(From mobile)

On Jul 10, 2016, at 2:22 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello Chris,

my main concern for this change is the lack of extensibility of metatypes. We can access the metatype through the .self postfix, which potentially will be removed in the future (only the postfix). Through an instance of such a metatype we can then access the static members and all initializer for our type.

SomeType.staticMember equals metatypeInstance.staticMemeber
SomeType.init equals metatypeInstance.init
It is also possible to extract some more information from the metatypes with the help of Buildin wrapper functions like sizeof or strideof. And the last but not the least we can use metatypes in type checking scenarios.

Last weak I asked the community if there is a need for all metatypes conform to the Hashable protocol, which would be really handy. The overall response was positive, even if only a handful of people responded.

The whole idea of metatypes conforming to Hashable kept me thinking how this might be solved in the future. The main problem is that metatypes provide direct access to initializer and static members of the type and a conformance to Hashable seems to be impossible without some compiler magic or a hidden hashValue property. The main confusion here might be that metatypes can be stored (at least while runtime) inside variables and pretend to be instances of a T.Type type.

That said, I’d like to consider of sealing the direct access to a metatype into a real type for the sake of extensibility and clarity.

I’m aware that my bikeshedding introduced more boilerplate in terms of accessing the initializer and static members through a bottleneck property called sealed. Furthermore there is one more character to type if we’d go with Type<T> instead of T.Type.

This is why this is a discussion before making any further decisions. :slight_smile:

PS: As I showed in my last code example a wrapper type Type<T> can also provide a few more information about the metatype.

Type<T>.size
Type<T>.stride
Type<T>.alignment
Furthermore sealing T.Type into Type<T> does solve the problem with the array/dictionary shorthand syntax.

Only when one would store/pass a type (name) we’d implicitly convert SomeType to Type<SomeType>.

Declarations do not follow this implicit conversion:

func foo(_: SomeType) is not equivalent to func foo(_: Type<SomeType>)
Array<SomeType>() == [SomeType]() but is not equivalent to [Type<SomeType>]() == Array<Type<SomeType>>()
Here is again my bikeshedding:

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
       
    // `metatype` is a better name than `sealed`
    // The tradeoff is this bottleneck property

    public let metatype: T.Type
        
    public let hashValue: Int
        
    public static var size: Int { get }
    public static var stride: Int { get }
    public static var alignment: Int { get }
        
    public var size: Int { get }
    public var stride: Int { get }
    public var alignment: Int { get }
        
    public var description: String { get }
    public var debugDescription: String { get }
}

public func ==<T, U>(lhs: Type<T>, rhs: Type<U>) -> Bool

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 00:18:02, Chris Lattner (clattner@apple.com) schrieb:

Hi Adrian,

What’s the motivation for this change?

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


(Adrian Zubarev) #10

Here is a part of a draft I put together in case we should go for a review on this.

Here is the formatted version if you cannot read it here: https://gist.github.com/DevAndArtist/a8d11011a376d47d0afc941017e64c75

Proposed solution

I propose to seal T.Type into a standalone type Type<T> and disallow .Type in public declaration usage.

Behavior Changes:

Make a distinction between internal and public .self.

Rename internal version of .self to .metatype or revision SE–0090 for Swift 3 and leave .self internally.

Make public .self (if not dropped for Swift 3) construct an instance ofType<T> instead a metatype T.Type.

Update all codebase to use Type<T> instead of T.Type.

Small example:

- public func sizeof<T>(_: T.Type) -> Int
+ public func sizeof<T>(_: Type<T>) -> Int
     
    // Current implementation
- return Int(Builtin.sizeof(T.self))

    // Possible internal implementation
+ return Int(Builtin.sizeof(T.metatype))
     
    // Possible implementation with `Type<T>`
+ return Int(Builtin.sizeof(Type<T>.metatype))
}
Pros:

Removing ambiguity in array/dictionary shorthand syntax.
Extensibility around the metatype:
Conformance to Hashable protocol.
Direct access to metatype related information like size, stride or aligment:
Type<T>.size equivalent to current sizeof(T.self)
Type<T>.stride equivalent to current strideof(T.self)
Type<T>.alignment equivalent to current alignof(T.self)
A step to remove public .self magic.
Less magical types floating around the codebase.
Better future maintenance.
Cons:

A distinction between internal and public .self must be introduced.

Dynamic types must be tweaked.

Currently it’s possible to check the Type like this:

//
func isInt<T>(metatype: T.Type) -> Bool {
    return metatype is Int.Type
}
is cast must be tweaked after this proposal:

func isInt<T>(type: Type<T>) -> Bool {
          
    return type == Type<Int>()
              
    // Since we store/pass a type (name) in this check,
    // we can safely convert `Int` into an instance of `Type<Int>`
    //
    // The result would look like this:
              
    return type == Int
             
    // This is logically equivalent to `metatype is Int.Type`
}
Type<T> adds more code noise for declarations.
T.Type = 6 characters
Type<T> = 7 characters
Type<T> adds a bottleneck (static) property to access the metatype:
Direct access today T.self (in future T)
Metatype access after this proposal:
Direct access through static member: Type<T>.metatype
Access through an initialized instance: typeInstance.metatype
Detailed Design

Rules:

Whenever a type (name) is passed or stored, it will be implicitly converted to Type<SomeType> (no need for .self - SE–0090).
Declarations do not follow the rule #1.
These rules would imply the following behavior and potentially solve the ambiguity in array/dictionary shorthand syntax:

// Type: Type<T>
let variable1 = T

// Type: T
let variable2 = T.init()

// Type: T
let variable3 = T()

// Return-type of static member from T
let variable4 = T.staticMember

// Return-type of an Array<T> static member
let variable6 = [T].staticMember

// Return-type of a Dictionary<T, U> static member
let variable7 = [T: U].staticMember

// Type: Array<T>
let array1 = [T]()

// Type: Array<T>
let array2: [T] = []

// Type: Array<Type<T>>
let array3: [Type<T>] = []

// Type: Array<Type<T>>
let array4 = [Type<T>]()

// Type: Dictionary<T, U>
let dictionary1 = [T: U]()

// Type: Dictionary<T, U>
let dictionary2: [T: U] = [:]

// Type: Dictionary<Type<T>, U>
let dictionary3: [Type<T>: U] = [:]

// Type: Dictionary<Type<T>, U>
let dictionary4 = [Type<T>: U]()
Possible Implementation Design:

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

    /// Creates an instance of `Type<T>`.
    public init() {
     
        // Possible new internal access to the metatype
        self.metatype = T.metatype
         
        // Same hash calculation like in `ObjectIdentifier`
        let rawPointerMetatype = unsafeBitCast(T.metatype, to: Builtin.RawPointer.metatype)
        self.hashValue = Int(Builtin.ptrtoint_Word(rawPointerMetatype))
         
        // Iff the value of `size`, `stride` and `alignment` properties cannot
        // be changed due runtime, we could move the calculation directly to
        // the initializer and make these properties to constants!
    }
     
    ///
    public let metatype: T.Type
     
    /// The Type<T>'s hash value.
    ///
    /// Hash values are not guaranteed to be equal across different executions of
    /// your program. Do not save hash values to use during a future execution.
    public let hashValue: Int
     
    /// Returns the contiguous memory footprint of `T`.
    ///
    /// Does not include any dynamically-allocated or "remote" storage.
    /// In particular, when `T` is a class type, is the
    /// same regardless of how many stored properties `T` has.
    public var size: Int {
        return sizeof(self)
         
        // OR:
        return Int(Builtin.sizeof(T.metatype))
    }
     
    /// Returns the least possible interval between distinct instances of
    /// `T` in memory. The result is always positive.
    public var stride: Int {
        return strideof(self)
         
        // OR:
        return Int(Builtin.strideof_nonzero(T.metatype))
    }
     
    /// Returns the minimum memory alignment of `T`.
    public var alignment: Int {
        return alignof(self)
     
        // OR:
        return Int(Builtin.alignof(T.metatype))
    }
         
    /// A textual representation of `self`.
    public var description: String {
        return "Type<\(self.metatype)>"
    }
     
    /// A textual representation of `self`, suitable for debugging.
    public var debugDescription: String {
        return "<" + self.description
                   + " metatype: \(self.metatype)"
                   + " size: \(self.size)"
                   + " stride: \(self.stride)"
                   + " alignment: \(self.alignment)>"
    }
}

/// Static members for access metatype specific information
/// without constructing an instance of `Type<T>`:
///
/// - `Type<T>.metatype`
/// - `Type<T>.size`
/// - `Type<T>.stride`
/// - `Type<T>.alignment`
public extension Type {
     
    ///
    public static var metatype: T.Type {
        return T.metatype
    }
     
    /// Returns the contiguous memory footprint of `T`.
    ///
    /// Does not include any dynamically-allocated or "remote" storage.
    /// In particular, when `T` is a class type, is the
    /// same regardless of how many stored properties `T` has.
    public static var size: Int {
        return sizeof(self)
         
        // OR:
        return Int(Builtin.sizeof(T.metatype))
    }
     
    /// Returns the least possible interval between distinct instances of
    /// `T` in memory. The result is always positive.
    public static var stride: Int {
        return strideof(self)
         
        // OR:
        return Int(Builtin.strideof_nonzero(T.metatype))
    }
     
    /// Returns the minimum memory alignment of `T`.
    public static var alignment: Int {
        return alignof(self)
     
        // OR:
        return Int(Builtin.alignof(T.metatype))
    }
}

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

···

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 18:03:17, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Just to be crystal clear, here is my vision.

Currently Swift is heading in this direction:

SomeType.staticMember == metatypeInstance.staticMember == SomeType.self.staticMember
SomeType.init == metatypeInstance.init == SomeType.self.init

SomeType.self -> SomeType.Type (metatype)

SomeType -> SomeType.Type (removed .self)

SomeType.self is SomeType.Type == true

func foo<T>(metatype: T.Type) -> T {
      
    // lets pretend T conforms to an initializable protocol
    return metatype.init()
}
With some tweaking and a little tradeoff for Type<T> we’d get this model:

SomeType.staticMember == Type<SomeType>.metatype.staticMember
SomeType.init == Type<SomeType>.metatype.init

SomeType -> Type<SomeType>() (initialized instance; removed .self)

SomeType == Type<SomeType>() == true

SomeType is Type<SomeType>() == true

func foo<T>(type: Type<T>) -> T {
      
    print(type.size)
    print(type.debugDescription)
    print(type.hashValue)
      
    // lets pretend T conforms to an initializable protocol
    return type.metatype.init()
}

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 17:28:35, Adrian Zubarev (adrian.zubarev@devandartist.com) schrieb:

Can you point me to some reading? I’m curious what exactly you’re talking about!?

I found a negative side effect of sealing T.Type. The problem appears lies in the dynamic is cast.

Currently we could do something like this:

func isInt<T>(metatype: T.Type) -> Bool {
       
    return metatype is Int.Type
}
There is no way to express the same type check with Type<T> except this:

func isInt<T>(type: Type<T>) -> Bool {
       
    return type == Type<Int>()
       
    // since this check is something where we store/pass a type (name)
    // we could implicitly convert `Int` into an instance of `Type<Int>`
    //
    // The result would look like this:
       
    return type == Int // which is logically equivalent to `metatype is Int.Type`
}

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 16:24:50, L. Mihalkovic (laurent.mihalkovic@gmail.com) schrieb:

It looks like this is touching on a small subset of the not-yet-designed reflection API (still tabled for 4.0?). I am interested to see what balance it strikes between declarations (eg. being able to reflect extensions declarations), types (eg reflecting type conformance), and operations (xxx.newInstance(), xxx.setValue()). The mirror api shows a general direction, maybe there is a way to squeeze a tiny subset in 3.0 (just like was done with P&Q that will remain a degenerate case of the upcoming more general syntax). Maybe just enough for people not to have to use ObjectIdentifier(:Any.Type).

Regards
(From mobile)

On Jul 10, 2016, at 2:22 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

Hello Chris,

my main concern for this change is the lack of extensibility of metatypes. We can access the metatype through the .self postfix, which potentially will be removed in the future (only the postfix). Through an instance of such a metatype we can then access the static members and all initializer for our type.

SomeType.staticMember equals metatypeInstance.staticMemeber
SomeType.init equals metatypeInstance.init
It is also possible to extract some more information from the metatypes with the help of Buildin wrapper functions like sizeof or strideof. And the last but not the least we can use metatypes in type checking scenarios.

Last weak I asked the community if there is a need for all metatypes conform to the Hashable protocol, which would be really handy. The overall response was positive, even if only a handful of people responded.

The whole idea of metatypes conforming to Hashable kept me thinking how this might be solved in the future. The main problem is that metatypes provide direct access to initializer and static members of the type and a conformance to Hashable seems to be impossible without some compiler magic or a hidden hashValue property. The main confusion here might be that metatypes can be stored (at least while runtime) inside variables and pretend to be instances of a T.Type type.

That said, I’d like to consider of sealing the direct access to a metatype into a real type for the sake of extensibility and clarity.

I’m aware that my bikeshedding introduced more boilerplate in terms of accessing the initializer and static members through a bottleneck property called sealed. Furthermore there is one more character to type if we’d go with Type<T> instead of T.Type.

This is why this is a discussion before making any further decisions. :slight_smile:

PS: As I showed in my last code example a wrapper type Type<T> can also provide a few more information about the metatype.

Type<T>.size
Type<T>.stride
Type<T>.alignment
Furthermore sealing T.Type into Type<T> does solve the problem with the array/dictionary shorthand syntax.

Only when one would store/pass a type (name) we’d implicitly convert SomeType to Type<SomeType>.

Declarations do not follow this implicit conversion:

func foo(_: SomeType) is not equivalent to func foo(_: Type<SomeType>)
Array<SomeType>() == [SomeType]() but is not equivalent to [Type<SomeType>]() == Array<Type<SomeType>>()
Here is again my bikeshedding:

public struct Type<T> : Hashable, CustomStringConvertible, CustomDebugStringConvertible {
        
    // `metatype` is a better name than `sealed`
    // The tradeoff is this bottleneck property

    public let metatype: T.Type
         
    public let hashValue: Int
         
    public static var size: Int { get }
    public static var stride: Int { get }
    public static var alignment: Int { get }
         
    public var size: Int { get }
    public var stride: Int { get }
    public var alignment: Int { get }
         
    public var description: String { get }
    public var debugDescription: String { get }
}

public func ==<T, U>(lhs: Type<T>, rhs: Type<U>) -> Bool

--
Adrian Zubarev
Sent with Airmail

Am 10. Juli 2016 um 00:18:02, Chris Lattner (clattner@apple.com) schrieb:

Hi Adrian,

What’s the motivation for this change?

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


(Anton Zhilin) #11

Why can't we drop metatypes T.Type with your proposal? Do they bring some
extra capabilities over Type<T> struct?


(Adrian Zubarev) #12

This isn’t a full proposal (yet). We still can change things. I didn’t consider everything and can’t to that on my own. Feedback is welcome.

To answer your question, we still need the metatype to access initializer and static member of that type. If we’d drop T.Type completely, we’d lose functionality to do so.

protocol A {
    init()
}

func foo<T : A>(metatype: T.Type) -> T {
    return metatype.init()
}
The downside of this proposal is the bottleneck property to access that functionality, otherwise adding Hashable or other things like size (MemoryLayout) won’t be possible without serious compiler magic. So do I believe.

func foo<T : A>(type: Type<T>) -> T {
    return type.metatype.init()
}
This is huge tradeoff, but it’s worth considering if the community can life with that.

Furthermore as already been mentioned in this thread, we might consider to extend Type<T> to add reflection functionality to it. (If we’d decide to go in that direction.)

···

--
Adrian Zubarev
Sent with Airmail

Am 13. Juli 2016 um 13:15:02, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

Why can't we drop metatypes T.Type with your proposal? Do they bring some extra capabilities over Type<T> struct?


(Anton Zhilin) #13

func foo<T : A>(metatype: Type<T>) -> T {
    return T()
}

I would prefer to remove metatypes T.Type completely and replace them with
your somewhat transparent Type<T>.

···

2016-07-13 15:02 GMT+03:00 Adrian Zubarev via swift-evolution < swift-evolution@swift.org>:

To answer your question, we still need the metatype to access initializer
and static member of that type. If we’d drop T.Type completely, we’d lose
functionality to do so.

protocol A {
    init()
}

func foo<T : A>(metatype: T.Type) -> T {
    return metatype.init()
}

In such cases, we can always refer to the type directly:


(L Mihalkovic) #14

Regards
LM
(From mobile)

This isn’t a full proposal (yet). We still can change things. I didn’t consider everything and can’t to that on my own. Feedback is welcome.

To answer your question, we still need the metatype to access initializer and static member of that type. If we’d drop T.Type completely, we’d lose functionality to do so.

protocol A {
    init()
}

func foo<T : A>(metatype: T.Type) -> T {
    return metatype.init()
}
The downside of this proposal is the bottleneck property to access that functionality, otherwise adding Hashable or other things like size (MemoryLayout) won’t be possible without serious compiler magic. So do I believe.

func foo<T : A>(type: Type<T>) -> T {
    return type.metatype.init()
}
This is huge tradeoff, but it’s worth considering if the community can life with that.

Furthermore as already been mentioned in this thread, we might consider to extend Type<T> to add reflection functionality to it. (If we’d decide to go in that direction.)

IMHO it is not just a matter of 'adding reflection'.. this IS a subset of reflection. The key is to design it question exhaustively enough to leave clean holes for the rest being only additive. Otherwise this will lead the way to more 'oops we have to change the proposal so that what we loose now can be put back additively later' (reference to the recent dance around the labels in sigs proposal that had to be patched post-acceptance)

···

On Jul 13, 2016, at 2:02 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

--
Adrian Zubarev
Sent with Airmail

Am 13. Juli 2016 um 13:15:02, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

Why can't we drop metatypes T.Type with your proposal? Do they bring some extra capabilities over Type<T> struct?

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


(Adrian Zubarev) #15

Okay I’m convinced on that one, but do we really want to give up being able to construct an instance from an metatype instance? (Personally I’d keep it as an optional feature.)

There are more than 7000 search results inside swift repository for .Type.

I looked up a few of them and I found an interesting example:

a = SomeClass.self
expectTrue(a as? Any.Type == SomeClass.self)
expectTrue(a as? AnyClass == SomeClass.self)
expectTrue(a as? SomeClass.Type == SomeClass.self)
After this proposal we had this:

// How can we cast from `Type<SomeClass>` to `Type<Any>`?
// Comparing two Type<T> is done with `hashValue` at least in my
// implementation.
expectTrue(a as? Type<Any> == SomeClass) // `SomeClass` equals `Type<SomeClass>()`

// How can we cast `Type<SomeClass>` to `AnyClass`?
expectTrue(a as? AnyClass == SomeClass)

// this is fine
expectTrue(a as? Type<SomeClass> == SomeClass)
Dynamic casts do not work with other instances of metatypes (I feel like there was a proposal for this, but I can’t find it). If we had this, we could fix the infix == function to compare metatypes instead the hash value.

What about dynamicType?

Any ideas?

···

a: Type<SomeClass> = SomeClass

--
Adrian Zubarev
Sent with Airmail

Am 13. Juli 2016 um 16:17:43, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

2016-07-13 15:02 GMT+03:00 Adrian Zubarev via swift-evolution <swift-evolution@swift.org>:
To answer your question, we still need the metatype to access initializer and static member of that type. If we’d drop T.Type completely, we’d lose functionality to do so.

protocol A {
    init()
}

func foo<T : A>(metatype: T.Type) -> T {
    return metatype.init()
}
In such cases, we can always refer to the type directly:
func foo<T : A>(metatype: Type<T>) -> T {
    return T()
}
I would prefer to remove metatypes T.Type completely and replace them with your somewhat transparent Type<T>.


(Adrian Zubarev) #16

I’m investing all my strength to create such a design. Stay patient and feel free to provide feedback to the our vision of the implementation (WIP): https://gist.github.com/DevAndArtist/a5744f21107812b2d4e6baee2c55e0bf

···

--
Adrian Zubarev
Sent with Airmail

Am 15. Juli 2016 um 17:17:44, L. Mihalkovic (laurent.mihalkovic@gmail.com) schrieb:

Regards
LM
(From mobile)

On Jul 13, 2016, at 2:02 PM, Adrian Zubarev via swift-evolution <swift-evolution@swift.org> wrote:

This isn’t a full proposal (yet). We still can change things. I didn’t consider everything and can’t to that on my own. Feedback is welcome.

To answer your question, we still need the metatype to access initializer and static member of that type. If we’d drop T.Type completely, we’d lose functionality to do so.

protocol A {
    init()
}

func foo<T : A>(metatype: T.Type) -> T {
    return metatype.init()
}
The downside of this proposal is the bottleneck property to access that functionality, otherwise adding Hashable or other things like size (MemoryLayout) won’t be possible without serious compiler magic. So do I believe.

func foo<T : A>(type: Type<T>) -> T {
    return type.metatype.init()
}
This is huge tradeoff, but it’s worth considering if the community can life with that.

Furthermore as already been mentioned in this thread, we might consider to extend Type<T> to add reflection functionality to it. (If we’d decide to go in that direction.)

IMHO it is not just a matter of 'adding reflection'.. this IS a subset of reflection. The key is to design it question exhaustively enough to leave clean holes for the rest being only additive. Otherwise this will lead the way to more 'oops we have to change the proposal so that what we loose now can be put back additively later' (reference to the recent dance around the labels in sigs proposal that had to be patched post-acceptance)

--
Adrian Zubarev
Sent with Airmail

Am 13. Juli 2016 um 13:15:02, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

Why can't we drop metatypes T.Type with your proposal? Do they bring some extra capabilities over Type<T> struct?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Anton Zhilin) #17

I see model of Type<T> as follows:

   1. Values of Type<T> are identifiers of types (8 bytes, I guess)
   2. All used identifiers are contained in Type<Any>
   3. Type<T> contains a subset of those identifiers
   4. Type<T> are usually created with default constructor Type<T>()
   5. Upcasting uses constructor init<U: T>(upcasting: Type<U>)
   6. Checking for conformance uses method isSubtype(of:)
   7. Size of Type<Type<T>> is 8, static size of Type<SomeProtocol> is 0

···

2016-07-13 18:25 GMT+03:00 Adrian Zubarev via swift-evolution < swift-evolution@swift.org>:

a = SomeClass.self

expectTrue(a as? Any.Type == SomeClass.self)
expectTrue(a as? AnyClass == SomeClass.self)
expectTrue(a as? SomeClass.Type == SomeClass.self)

That example just demonstrates syntax of metatypes and does not count.

   -

   What about dynamicType?

It should have the following signature:

func dynamicType<T>(_ var: T) -> Type<T>

To return Type<T> means to return an identifier of U: T


(Adrian Zubarev) #18

Am 13. Juli 2016 um 18:30:53, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

I see model of Type<T> as follows:
Values of Type<T> are identifiers of types (8 bytes, I guess)
All used identifiers are contained in Type<Any>
Type<T> contains a subset of those identifiers
I can’t follow your though here. Is this a totally new behavior?

Type<T> are usually created with default constructor Type<T>()
That one is easy and already there.

Upcasting uses constructor init<U: T>(upcasting: Type<U>)
It does look neat but I can’t implement it. At least I have no clue how to solve the problem that `T` is not a protocol or a class type.

Checking for conformance uses method isSubtype(of:)
Interesting suggestion.

Size of Type<Type<T>> is 8, static size of Type<SomeProtocol> is 0
I feel like you mean something different here than this:


protocol SomeProtocol {}

sizeof(SomeProtocol.self) == 40
···

2016-07-13 18:25 GMT+03:00 Adrian Zubarev via swift-evolution <swift-evolution@swift.org>:
a = SomeClass.self

expectTrue(a as? Any.Type == SomeClass.self)
expectTrue(a as? AnyClass == SomeClass.self)
expectTrue(a as? SomeClass.Type == SomeClass.self)
That example just demonstrates syntax of metatypes and does not count.
What about dynamicType?

It should have the following signature:

func dynamicType<T>(_ var: T) -> Type<T>

To return Type<T> means to return an identifier of U: T
Was this a typo?

func dynamicType<U: T>(_ var: T) -> Type<U>

PS: If you’d like to join on this idea, I could move the draft to a repo to be able to write the proposal together. You had some good catchy ideas there.


(Anton Zhilin) #19

Am 13. Juli 2016 um 18:30:53, Anton Zhilin (antonyzhilin@gmail.com)
schrieb:

I see model of Type<T> as follows:

   1. Values of Type<T> are identifiers of types (8 bytes, I guess)
   2. All used identifiers are contained in Type<Any>
   3. Type<T> contains a subset of those identifiers

I can’t follow your though here. Is this a totally new behavior?

That's how it works right now:

sizeofValue(Bool.self) //=> 0 (optimized away)
sizeofValue(Bool.self as Any.self) //=> 8

I'll put my thoughts another way:

   - Internally, Type<T> contains an Int, i.e. identifer of a type. For
   every type, compiler must choose a different identifier
   - API of Type<T> is defined so that it can only contain identifiers of
   subtypes of T
   - For example, when you get a variable of Type<BaseClass>, it can
   correspond to BaseClass or DerivedClass

   1. Upcasting uses constructor init<U: T>(upcasting: Type<U>)

It does look neat but I can’t implement it. At least I have no clue how to
solve the problem that `T` is not a protocol or a class type.

We should add implicit convertion of Type<U> to Type<T>, dulicating current
behaviour of `as T.Type`.
That constructor still can be useful, but it will be failable and not that
significant in practise.
I don't like adding compiler magic, but I guess we really should in this
case, because otherwise Type<T> can't replace T.Type.

   1. Size of Type<Type<T>> is 8, static size of Type<SomeProtocol> is 0

I feel like you mean something different here than this:


protocol SomeProtocol {}

sizeof(SomeProtocol.self) == 40

That was a mistake. Static size of Type<SomeProtocol> will be 40, and size
property of its values can be less or greater than 40, depending on
internal type identifier.

···

2016-07-13 20:19 GMT+03:00 Adrian Zubarev via swift-evolution < swift-evolution@swift.org>:


(Adrian Zubarev) #20

Okay I get it now. You meant the size for a metatype sizeof(T.Type.self). This is indeed 8 bytes, at least not for optimized Bool (as you showed).

Internally, Type contains an Int, i.e. identifier of a type. For every type, compiler must choose a different identifier
We can already implement this with Hashable protocol.

ObjectIdentifier: A unique identifier for a class instance or metatype.

In Swift, only class instances and metatypes have unique identities. There is no notion of identity for structs, enums, functions, or tuples.
// version 1:
public let hashValue: Int = ObjectIdentifier(T.self).hashValue

// version 2 (uses ObjectIdentifier calculation without
// constructing an instance of ObjectIdentifier):

init() {
    // calculate the hashValue only once
    // I'd rename `.self` to `.metatype`
     
    let rawPointerMetatype = unsafeBitCast(T.self, to: Builtin.RawPointer.self)
    self.hashValue = Int(Builtin.ptrtoint_Word(rawPointerMetatype))
}

public let hashValue: Int
API of Type is defined so that it can only contain identifiers of subtypes of T

For example, when you get a variable of Type, it can correspond to BaseClass or DerivedClass
I did a quick test and I feel like this falls under the part of tweaking dynamic casts to work with Type<T>. There is no special compiler magic needed, unsafeBitCast should do the trick. Or we should teach dynamic casts to work with the inner type T instead of the whole Type<T>.

public struct Type<T> : Hashable {
     
    public let metatype: T.Type = T.self

    public let hashValue: Int = ObjectIdentifier(T.self).hashValue
}

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

public func asOptionalCast<U, T>(type: Type<T>) -> Type<U>? {
     
    guard (type.metatype as? U.Type) != nil else {
        return nil
    }
    return unsafeBitCast(type, to: Type<U>.self)
}

class A {}
class B: A {}

let typeB = Type<B>()

// downcast Type<B> to Type<A>
let downcast: Type<A>? = asOptionalCast(type: typeB)

(downcast! == typeB) == true

// cast Type<A> (which here is Type<B>) back to Type<B>
let upcast: Type<B>? = asOptionalCast(type: downcast!)

(upcast! == Type<B>()) == true
The good part here that the hash value of the casted type won’t change and testing against a new instance of the same dynamic type will be always true.

···

--
Adrian Zubarev
Sent with Airmail

Am 13. Juli 2016 um 20:31:22, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

2016-07-13 20:19 GMT+03:00 Adrian Zubarev via swift-evolution <swift-evolution@swift.org>:
Am 13. Juli 2016 um 18:30:53, Anton Zhilin (antonyzhilin@gmail.com) schrieb:

I see model of Type<T> as follows:
Values of Type<T> are identifiers of types (8 bytes, I guess)
All used identifiers are contained in Type<Any>
Type<T> contains a subset of those identifiers
I can’t follow your though here. Is this a totally new behavior?

That's how it works right now:

sizeofValue(Bool.self) //=> 0 (optimized away)
sizeofValue(Bool.self as Any.self) //=> 8

I'll put my thoughts another way:
Internally, Type<T> contains an Int, i.e. identifer of a type. For every type, compiler must choose a different identifier
API of Type<T> is defined so that it can only contain identifiers of subtypes of T
For example, when you get a variable of Type<BaseClass>, it can correspond to BaseClass or DerivedClass
Upcasting uses constructor init<U: T>(upcasting: Type<U>)
It does look neat but I can’t implement it. At least I have no clue how to solve the problem that `T` is not a protocol or a class type.

We should add implicit convertion of Type<U> to Type<T>, dulicating current behaviour of `as T.Type`.
That constructor still can be useful, but it will be failable and not that significant in practise.
I don't like adding compiler magic, but I guess we really should in this case, because otherwise Type<T> can't replace T.Type.
Size of Type<Type<T>> is 8, static size of Type<SomeProtocol> is 0
I feel like you mean something different here than this:


protocol SomeProtocol {}

sizeof(SomeProtocol.self) == 40

That was a mistake. Static size of Type<SomeProtocol> will be 40, and size property of its values can be less or greater than 40, depending on internal type identifier.