[Idea] Type erasure for protocols with Self or associated type requirements.


(Vatsal Manot) #1

(This is my first time on swift-evolution / Mailman, I apologize for any formatting errors)

As we know, the following fails:

protocol _Protocol
{
    static var _Parameter: Any.Type { get }
    
    var nonGenericVariable: Int { get }
    var _parameter: Any { get set }
    
    func _inputParameter(_: Any) -> Any?
}

Protocol.self

with the error:

protocol 'Protocol' can only be used as a generic constraint because it has Self or associated type requirements

For quite some time, I have been using a particular workaround for this:

protocol _Protocol
{
    static var _Parameter: Any.Type { get }
    
    var nonGenericVariable: Int { get }
    var _parameter: Any { get set }
    
    func _inputParameter(_: Any) -> Any?
}

extension _Protocol where Self: Protocol
{
    static var _Parameter: Any.Type
    {
        return Parameter.self
    }
    
    var _parameter: Any
    {
        get
        {
            return parameter
        }
        
        set
        {
            parameter = newValue as! Parameter
        }
    }
    
    func _inputParameter(parameter: Any) -> Any?
    {
        return (parameter as? Parameter).map(inputParameter)
    }
}

protocol Protocol: _Protocol
{
    typealias Parameter
    
    var nonGenericVariable: Int { get }
    var parameter: Parameter { get set }
    
    func inputParameter(_: Parameter) -> Parameter
}

And it has worked well so far.

The idea is to let the compiler generate these type-erased ‘versions’ on demand, using syntax like:

Protocol.Opaque

The rough procedure of creating a type-erased version for a given protocol is as follows:

- Create a protocol with an underscore prefixed to the name of the target protocol.

- Expose all the non-ATD (self or associated type dependent) constructs as requirements (essentially just copying the declarations)

- Expose all the ATD constructs as requirements with associated types replaced with “Any”, with an underscore prefixed to their name changing the return value of functions accepting ATD inputs to an optional version of the same type.

- Extend the target protocol with implementations of these type-erased ‘versions’ of ATD constructs. For variables, a computed property is provided which returns the target variable, and force casts newValue in the setter (if any) to the required ATD type. For functions, the parameters undergo an attempted cast to the required ATD types, and are then mapped over the original implementation. Functions without ATD parameters can return just Any, because there is no casting involved.

- Initializers become static functions with prefixed underscores, returning an Optional<Protocol.Opaque>

I admit that I don’t have a plan on how this should be implemented. My fear is that it require tremendous amounts of metadata, bloating the binary size, and so I have submitted this as an idea and not a proposal.

I have created a gist (https://gist.github.com/vmanot/888afc3f26caf142cd21) as a demonstration.

This pattern has helped on innumerable occasions, and is used in my projects to allow some runtime tricks. It also helps me forward implementations in a way similar (behavior-wise) to “AnyGenerator”

import Swift

struct AnyGenerator<Element>
{
    var base: _GeneratorType
    
    init<G: protocol<_GeneratorType, GeneratorType> where G.Element == Element>(base: G)
    {
        self.base = base
    }
    
    mutating func next() -> Element?
    {
        return (base._next() as! Optional<Element>)
    }
}

AnyGenerator(base: [1, 2, 3].generate())

(I have tested this with an actual_GeneratorType implementation, and it works)

Please let me know what you think of this idea, and whether it is viable or not.


(Howard Lovatt) #2

I have proposed something very similar:

    [swift-evolution] Make generics covariant and add generics to protocols <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160111/006367.html> <>

Although I didn’t mention this in the proposal I am aware that if that proposal was accepted then Self could become a shorthand for the type name and loose all its ‘magic’ properties and would be in effect what you are suggesting.

···

On 12 Jan 2016, at 5:44 PM, Vatsal Manot via swift-evolution <swift-evolution@swift.org> wrote:

(This is my first time on swift-evolution / Mailman, I apologize for any formatting errors)

As we know, the following fails:

protocol _Protocol
{
    static var _Parameter: Any.Type { get }
    
    var nonGenericVariable: Int { get }
    var _parameter: Any { get set }
    
    func _inputParameter(_: Any) -> Any?
}

Protocol.self

with the error:

protocol 'Protocol' can only be used as a generic constraint because it has Self or associated type requirements

For quite some time, I have been using a particular workaround for this:

protocol _Protocol
{
    static var _Parameter: Any.Type { get }
    
    var nonGenericVariable: Int { get }
    var _parameter: Any { get set }
    
    func _inputParameter(_: Any) -> Any?
}

extension _Protocol where Self: Protocol
{
    static var _Parameter: Any.Type
    {
        return Parameter.self
    }
    
    var _parameter: Any
    {
        get
        {
            return parameter
        }
        
        set
        {
            parameter = newValue as! Parameter
        }
    }
    
    func _inputParameter(parameter: Any) -> Any?
    {
        return (parameter as? Parameter).map(inputParameter)
    }
}

protocol Protocol: _Protocol
{
    typealias Parameter
    
    var nonGenericVariable: Int { get }
    var parameter: Parameter { get set }
    
    func inputParameter(_: Parameter) -> Parameter
}

And it has worked well so far.

The idea is to let the compiler generate these type-erased ‘versions’ on demand, using syntax like:

Protocol.Opaque

The rough procedure of creating a type-erased version for a given protocol is as follows:

- Create a protocol with an underscore prefixed to the name of the target protocol.

- Expose all the non-ATD (self or associated type dependent) constructs as requirements (essentially just copying the declarations)

- Expose all the ATD constructs as requirements with associated types replaced with “Any”, with an underscore prefixed to their name changing the return value of functions accepting ATD inputs to an optional version of the same type.

- Extend the target protocol with implementations of these type-erased ‘versions’ of ATD constructs. For variables, a computed property is provided which returns the target variable, and force casts newValue in the setter (if any) to the required ATD type. For functions, the parameters undergo an attempted cast to the required ATD types, and are then mapped over the original implementation. Functions without ATD parameters can return just Any, because there is no casting involved.

- Initializers become static functions with prefixed underscores, returning an Optional<Protocol.Opaque>

I admit that I don’t have a plan on how this should be implemented. My fear is that it require tremendous amounts of metadata, bloating the binary size, and so I have submitted this as an idea and not a proposal.

I have created a gist (https://gist.github.com/vmanot/888afc3f26caf142cd21) as a demonstration.

This pattern has helped on innumerable occasions, and is used in my projects to allow some runtime tricks. It also helps me forward implementations in a way similar (behavior-wise) to “AnyGenerator”

import Swift

struct AnyGenerator<Element>
{
    var base: _GeneratorType
    
    init<G: protocol<_GeneratorType, GeneratorType> where G.Element == Element>(base: G)
    {
        self.base = base
    }
    
    mutating func next() -> Element?
    {
        return (base._next() as! Optional<Element>)
    }
}

AnyGenerator(base: [1, 2, 3].generate())

(I have tested this with an actual_GeneratorType implementation, and it works)

Please let me know what you think of this idea, and whether it is viable or not.

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


(John McCall) #3

(This is my first time on swift-evolution / Mailman, I apologize for any formatting errors)

As we know, the following fails:

protocol _Protocol
{
    static var _Parameter: Any.Type { get }
    
    var nonGenericVariable: Int { get }
    var _parameter: Any { get set }
    
    func _inputParameter(_: Any) -> Any?
}

Protocol.self

with the error:

protocol 'Protocol' can only be used as a generic constraint because it has Self or associated type requirements

For quite some time, I have been using a particular workaround for this:

protocol _Protocol
{
    static var _Parameter: Any.Type { get }
    
    var nonGenericVariable: Int { get }
    var _parameter: Any { get set }
    
    func _inputParameter(_: Any) -> Any?
}

extension _Protocol where Self: Protocol
{
    static var _Parameter: Any.Type
    {
        return Parameter.self
    }
    
    var _parameter: Any
    {
        get
        {
            return parameter
        }
        
        set
        {
            parameter = newValue as! Parameter
        }
    }
    
    func _inputParameter(parameter: Any) -> Any?
    {
        return (parameter as? Parameter).map(inputParameter)
    }
}

protocol Protocol: _Protocol
{
    typealias Parameter
    
    var nonGenericVariable: Int { get }
    var parameter: Parameter { get set }
    
    func inputParameter(_: Parameter) -> Parameter
}

And it has worked well so far.

The idea is to let the compiler generate these type-erased ‘versions’ on demand, using syntax like:

Protocol.Opaque

The rough procedure of creating a type-erased version for a given protocol is as follows:

- Create a protocol with an underscore prefixed to the name of the target protocol.

- Expose all the non-ATD (self or associated type dependent) constructs as requirements (essentially just copying the declarations)

- Expose all the ATD constructs as requirements with associated types replaced with “Any”, with an underscore prefixed to their name changing the return value of functions accepting ATD inputs to an optional version of the same type.

- Extend the target protocol with implementations of these type-erased ‘versions’ of ATD constructs. For variables, a computed property is provided which returns the target variable, and force casts newValue in the setter (if any) to the required ATD type. For functions, the parameters undergo an attempted cast to the required ATD types, and are then mapped over the original implementation. Functions without ATD parameters can return just Any, because there is no casting involved.

- Initializers become static functions with prefixed underscores, returning an Optional<Protocol.Opaque>

I admit that I don’t have a plan on how this should be implemented. My fear is that it require tremendous amounts of metadata, bloating the binary size, and so I have submitted this as an idea and not a proposal.

We’re already slowly progressing towards an implementation design which will allow us to simply lift some of these restrictions. The work will be done in the Swift 3 time-frame; whether we actually lift the restrictions on existential types significantly in this release is a lot less certain, but there’s not much point in having the discussion right now, I think.

John.

···

On Jan 11, 2016, at 10:44 PM, Vatsal Manot via swift-evolution <swift-evolution@swift.org> wrote:

I have created a gist (https://gist.github.com/vmanot/888afc3f26caf142cd21) as a demonstration.

This pattern has helped on innumerable occasions, and is used in my projects to allow some runtime tricks. It also helps me forward implementations in a way similar (behavior-wise) to “AnyGenerator”

import Swift

struct AnyGenerator<Element>
{
    var base: _GeneratorType
    
    init<G: protocol<_GeneratorType, GeneratorType> where G.Element == Element>(base: G)
    {
        self.base = base
    }
    
    mutating func next() -> Element?
    {
        return (base._next() as! Optional<Element>)
    }
}

AnyGenerator(base: [1, 2, 3].generate())

(I have tested this with an actual_GeneratorType implementation, and it works)

Please let me know what you think of this idea, and whether it is viable or not.

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


(Howard Lovatt) #4

@Vatsai,

Not sure I get what you mean. Can you expand the example?

-- Howard.

PS Re. Mailman: I always make sure a CC swift-evolution@swift.org

···

On 12 Jan 2016, at 10:47 PM, Vatsal Manot <vatsal.manot@yahoo.com> wrote:

Sure, covariance might solve some cases, but what if you had:

struct X<T where T: SomeProtocol>
{

}

Coercion to X<Any> would be impossible. What I’m aiming for is an opaque interface to any given protocol.

(On an unrelated note, if I want this reply to reflected in the archive, do I just /cc swift-evolution? A little new to Mailman, sorry.)

On 12-Jan-2016, at 3:29 PM, Howard Lovatt <howard.lovatt@gmail.com> wrote:

I have proposed something very similar:

    [swift-evolution] Make generics covariant and add generics to protocols

Although I didn’t mention this in the proposal I am aware that if that proposal was accepted then Self could become a shorthand for the type name and loose all its ‘magic’ properties and would be in effect what you are suggesting.

On 12 Jan 2016, at 5:44 PM, Vatsal Manot via swift-evolution <swift-evolution@swift.org> wrote:

(This is my first time on swift-evolution / Mailman, I apologize for any formatting errors)

As we know, the following fails:

protocol _Protocol
{
    static var _Parameter: Any.Type { get }
    
    var nonGenericVariable: Int { get }
    var _parameter: Any { get set }
    
    func _inputParameter(_: Any) -> Any?
}

Protocol.self

with the error:

protocol 'Protocol' can only be used as a generic constraint because it has Self or associated type requirements

For quite some time, I have been using a particular workaround for this:

protocol _Protocol
{
    static var _Parameter: Any.Type { get }
    
    var nonGenericVariable: Int { get }
    var _parameter: Any { get set }
    
    func _inputParameter(_: Any) -> Any?
}

extension _Protocol where Self: Protocol
{
    static var _Parameter: Any.Type
    {
        return Parameter.self
    }
    
    var _parameter: Any
    {
        get
        {
            return parameter
        }
        
        set
        {
            parameter = newValue as! Parameter
        }
    }
    
    func _inputParameter(parameter: Any) -> Any?
    {
        return (parameter as? Parameter).map(inputParameter)
    }
}

protocol Protocol: _Protocol
{
    typealias Parameter
    
    var nonGenericVariable: Int { get }
    var parameter: Parameter { get set }
    
    func inputParameter(_: Parameter) -> Parameter
}

And it has worked well so far.

The idea is to let the compiler generate these type-erased ‘versions’ on demand, using syntax like:

Protocol.Opaque

The rough procedure of creating a type-erased version for a given protocol is as follows:

- Create a protocol with an underscore prefixed to the name of the target protocol.

- Expose all the non-ATD (self or associated type dependent) constructs as requirements (essentially just copying the declarations)

- Expose all the ATD constructs as requirements with associated types replaced with “Any”, with an underscore prefixed to their name changing the return value of functions accepting ATD inputs to an optional version of the same type.

- Extend the target protocol with implementations of these type-erased ‘versions’ of ATD constructs. For variables, a computed property is provided which returns the target variable, and force casts newValue in the setter (if any) to the required ATD type. For functions, the parameters undergo an attempted cast to the required ATD types, and are then mapped over the original implementation. Functions without ATD parameters can return just Any, because there is no casting involved.

- Initializers become static functions with prefixed underscores, returning an Optional<Protocol.Opaque>

I admit that I don’t have a plan on how this should be implemented. My fear is that it require tremendous amounts of metadata, bloating the binary size, and so I have submitted this as an idea and not a proposal.

I have created a gist (https://gist.github.com/vmanot/888afc3f26caf142cd21) as a demonstration.

This pattern has helped on innumerable occasions, and is used in my projects to allow some runtime tricks. It also helps me forward implementations in a way similar (behavior-wise) to “AnyGenerator”

import Swift

struct AnyGenerator<Element>
{
    var base: _GeneratorType
    
    init<G: protocol<_GeneratorType, GeneratorType> where G.Element == Element>(base: G)
    {
        self.base = base
    }
    
    mutating func next() -> Element?
    {
        return (base._next() as! Optional<Element>)
    }
}

AnyGenerator(base: [1, 2, 3].generate())

(I have tested this with an actual_GeneratorType implementation, and it works)

Please let me know what you think of this idea, and whether it is viable or not.

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