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


(Marc Knaup) #1

Hey guys,

I'm looking at Swift 3.0's goal to improve generics.

Is there any info yet if and how we will be able to refer to instances of
protocols that have associated types?
What is the difficulty in supporting this?

Simple examples:
var list = [Hashable]()
var hashable: Hashable = 2

Right now all we get is

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

Thanks,
  Marc


(Dave Abrahams) #2

Hey guys,

I'm looking at Swift 3.0's goal to improve generics.

Is there any info yet if and how we will be able to refer to instances of protocols that have associated types?

As far as I know, this isn't a solvable problem.

What is the difficulty in supporting this?

Here's a simple example:

protocol P {
  typealias A
  var x: A { get set }
}

struct Y : P {
  var x: Int
}

struct Z : P {
  var x: String
}

func f(p1: P, p2: P) {
  p1.x = p2.x // assigning an Int to a String?
}
  

Simple examples:
var list = [Hashable]()
var hashable: Hashable = 2

Right now all we get is
protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements

In this case it's "self requirements" inherited from Equatable that make instances of Hashable impossible.
That said, providing for sets/dictionaries of heterogeneous values is a problem we're interested in solving in the standard library.

-Dave

···

On Dec 13, 2015, at 3:55 PM, Marc Knaup via swift-evolution <swift-evolution@swift.org> wrote:


(Paul Cantrell) #3

p1.x = p2.x should be a compiler error, because there’s not enough type information. But that shouldn’t stop a programmer from doing this:

protocol P {
  typealias A
  var x: A { get set }
  var y: Int
}

struct Y : P {
  var x: Int
  var y: Int
}

struct Z : P {
  var x: String
  var y: Int
}

func maxY(p1: P, p2: P) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

…right?

Cheers, P

···

On Dec 13, 2015, at 11:56 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 13, 2015, at 3:55 PM, Marc Knaup via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Is there any info yet if and how we will be able to refer to instances of protocols that have associated types?

As far as I know, this isn't a solvable problem.

What is the difficulty in supporting this?

Here's a simple example:

protocol P {
  typealias A
  var x: A { get set }
}

struct Y : P {
  var x: Int
}

struct Z : P {
  var x: String
}

func f(p1: P, p2: P) {
  p1.x = p2.x // assigning an Int to a String?
}


(Joe Groff) #4

Hey guys,

I'm looking at Swift 3.0's goal to improve generics.

Is there any info yet if and how we will be able to refer to instances of protocols that have associated types?

As far as I know, this isn't a solvable problem.

There are solutions to common patterns that we can and should support. If there are only associated types, then the dynamic type 'protocol<Fooable where Foo == SomeSpecificType> is often useful; the standard library itself fakes dynamic type containers for SequenceType and GeneratorType that do this.

For protocols like Equatable and Hashable with Self parameter requirements, it's true that the compiler can't implicitly produce an Equatable dynamic type that conforms to Equatable. There are a couple possible solutions to this. There's a fairly obvious generalized implementation that could be provided manually:

extension Equatable: Equatable {}
func ==(x: Equatable, y: Equatable) -> Bool {
  // If the values are the same type, use that type's == operator.
  // Pretend that 'as? dynamicType' works
  if let yAsX = y as? x.dynamicType {
    return x == yAsX
  }
  // Values of different type aren't ==.
  return false
}

We'd want to tweak the language rules for implicit promotion so that things like `1 == 1.0` don't implicitly choose the heterogeneous Equatable implementation of `==`, but this would otherwise allow for protocols to require Equatable and Hashable without forgoing the ability to be useful dynamic types.

Another possibility is to generalize Equatable so that a conforming type or refining protocol can control how dynamic its equatability is:

protocol Equatable {
  // The upper bound of type that this type can be equated with. Defaults to `Self`.
  typealias EquatesWith = Self

  // `EquatesWith` must be `Self` or a supertype of `Self`.
  where Self: EquatesWith

  func ==(_: EquatesWith, _: EquatesWith) -> Bool
}

A dynamic protocol can then refine Equatable or Hashable by equating with its own dynamic type, rather than defaulting to strict equatability:

protocol Drawable: Hashable {
  // All Drawables must be equatable with all other Drawables.
  where EquatesWith == Drawable

  func draw()
}

-Joe

···

On Dec 13, 2015, at 9:56 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 13, 2015, at 3:55 PM, Marc Knaup via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

What is the difficulty in supporting this?

Here's a simple example:

protocol P {
  typealias A
  var x: A { get set }
}

struct Y : P {
  var x: Int
}

struct Z : P {
  var x: String
}

func f(p1: P, p2: P) {
  p1.x = p2.x // assigning an Int to a String?
}
  

Simple examples:
var list = [Hashable]()
var hashable: Hashable = 2

Right now all we get is
protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements

In this case it's "self requirements" inherited from Equatable that make instances of Hashable impossible.
That said, providing for sets/dictionaries of heterogeneous values is a problem we're interested in solving in the standard library.

-Dave

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


(Tal Atlas) #5

This is the error that gets in my way the most when trying to do protocol
oriented programming. It can be super frustrating. That lead me to make a
proposal a few weeks back for having generic protocols.

In that proposal some swift core people expressed that they were keenly
aware of said pain. And while they didn't deliver details in time or
implementation they expressed that there were efforts ongoing to solve it.

···

On Sun, Dec 13, 2015 at 6:55 PM Marc Knaup via swift-evolution < swift-evolution@swift.org> wrote:

Hey guys,

I'm looking at Swift 3.0's goal to improve generics.

Is there any info yet if and how we will be able to refer to instances of
protocols that have associated types?
What is the difficulty in supporting this?

Simple examples:
var list = [Hashable]()
var hashable: Hashable = 2

Right now all we get is

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

Thanks,
  Marc

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


(Dave Abrahams) #6

Ah, but then you have the situation that P doesn't conform to P (it doesn't have an x that you can access). In my opinion that is just too weird an idea to be usable.
Personally, I have always thought that protocols intended to be used as unbound existentials (not P<A: Int>, just plain P) are different beasts entirely from the kind used as generic constraints and should be explicitly declared as such—I don't think I've ever seen a protocol that is well-used in both ways; if you have counterexamples I'd love to see them. In "existential protocols," declaring an associated type or creating a self requirement would be an error at the point of declaration.

-Dave

···

On Dec 13, 2015, at 11:03 PM, Paul Cantrell <cantrell@pobox.com> wrote:

On Dec 13, 2015, at 11:56 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 13, 2015, at 3:55 PM, Marc Knaup via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Is there any info yet if and how we will be able to refer to instances of protocols that have associated types?

As far as I know, this isn't a solvable problem.

What is the difficulty in supporting this?

Here's a simple example:

protocol P {
  typealias A
  var x: A { get set }
}

struct Y : P {
  var x: Int
}

struct Z : P {
  var x: String
}

func f(p1: P, p2: P) {
  p1.x = p2.x // assigning an Int to a String?
}

p1.x = p2.x should be a compiler error, because there’s not enough type information.

But that shouldn’t stop a programmer from doing this:

protocol P {
  typealias A
  var x: A { get set }
  var y: Int
}

struct Y : P {
  var x: Int
  var y: Int
}

struct Z : P {
  var x: String
  var y: Int
}

func maxY(p1: P, p2: P) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

…right?


(David Waite) #7

I recently gave a talk on protocols and protocol oriented programming, and while writing it I came up with three kinds of protocols:

- Basic protocols, without type aliases or parents with type aliases, usable against any value or class type
- Objective-C protocols, without the possibility of type aliases, restricted to being implemented by Objective-C classes and supporting optional methods
- “Deferred Type” protocols, with type aliases or a parent with a type alias, where conformance is too flexible for the protocol to be used safely without the compiler knowing the implementing type. These protocols are only usable in a context where the compiler can figure out the conforming type, which it then uses statically

I lamented that protocol is used to describe the third type as well as the first two.

-DW

···

On Dec 14, 2015, at 12:37 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:
<snip>
Personally, I have always thought that protocols intended to be used as unbound existentials (not P<A: Int>, just plain P) are different beasts entirely from the kind used as generic constraints and should be explicitly declared as such—I don't think I've ever seen a protocol that is well-used in both ways; if you have counterexamples I'd love to see them. In "existential protocols," declaring an associated type or creating a self requirement would be an error at the point of declaration.


(ilya) #8

You can achieve the same result more cleanly with

func maxY<T:P, U:P>(p1:T, p2: U) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

···

On Mon, Dec 14, 2015 at 10:03 AM, Paul Cantrell via swift-evolution < swift-evolution@swift.org> wrote:

On Dec 13, 2015, at 11:56 PM, Dave Abrahams via swift-evolution < > swift-evolution@swift.org> wrote:

On Dec 13, 2015, at 3:55 PM, Marc Knaup via swift-evolution < > swift-evolution@swift.org> wrote:

Is there any info yet if and how we will be able to refer to instances of
protocols that have associated types?

As far as I know, this isn't a solvable problem.

What is the difficulty in supporting this?

Here's a simple example:

protocol P {
  typealias A
  var x: A { get set }
}

struct Y : P {
  var x: Int
}

struct Z : P {
  var x: String
}

func f(p1: P, p2: P) {
  p1.x = p2.x // assigning an Int to a String?
}

p1.x = p2.x should be a compiler error, because there’s not enough type
information. But that shouldn’t stop a programmer from doing this:

protocol P {
  typealias A
  var x: A { get set }
  var y: Int
}

struct Y : P {
  var x: Int
  var y: Int
}

struct Z : P {
  var x: String
  var y: Int
}

func maxY(p1: P, p2: P) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

…right?

Cheers, P

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


(Andrew Bennett) #9

I agree that this is an issue, I'd really like to see a language feature to
address it.

In some circumstances you can work around the issue with type-erasure. You
implement a generic wrapper (like AnySequence) that can take anything
conforming to that protocol and expose it with a single concrete interface.

I learnt a lot implementing a few of these. There's a good article on it
here:
https://realm.io/news/type-erased-wrappers-in-swift/

I wouldn't be too surprised if Apple formalises type erasure somehow, the
implementation is very mechanical and could be automated. For example: If
you have a protocol MyProtocol that has type requirements then perhaps
MyProtocol.Any would refer to an automatically generated type erased
concrete type. I suppose you could also do:

typealias AB = protocol<A,B>
AB.Any

The main problem with current implementations seems to be, for example:
ZipSequence is a SequenceType, but ZipSequence is not an AnySequence.

Also you cannot necessarily conform to a protocol if it has things like
initialiser requirements.

···

On Tue, Dec 15, 2015 at 12:45 AM, Tal Atlas via swift-evolution < swift-evolution@swift.org> wrote:

This is the error that gets in my way the most when trying to do protocol
oriented programming. It can be super frustrating. That lead me to make a
proposal a few weeks back for having generic protocols.

In that proposal some swift core people expressed that they were keenly
aware of said pain. And while they didn't deliver details in time or
implementation they expressed that there were efforts ongoing to solve it.

On Sun, Dec 13, 2015 at 6:55 PM Marc Knaup via swift-evolution < > swift-evolution@swift.org> wrote:

Hey guys,

I'm looking at Swift 3.0's goal to improve generics.

Is there any info yet if and how we will be able to refer to instances of
protocols that have associated types?
What is the difficulty in supporting this?

Simple examples:
var list = [Hashable]()
var hashable: Hashable = 2

Right now all we get is

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

Thanks,
  Marc

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

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


(Dave Abrahams) #10

Keep in mind that a language feature like binding/constraining associated types is not a complete solution for real use-cases, so we would still need AnySequence<Element> in the library. Otherwise it would be SequenceType<Generator: SomeGenerator>, which binds a type that you don't actually want to expose.

-Dave

···

On Dec 14, 2015, at 6:23 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

It is definitely possible to solve this problem for associated types by binding or constraining them. The ML module system has a feature that is roughly analogous to this.


(Dave Abrahams) #11

IMO the fact that you created an a array of requests and wrote cancelAll above *demonstrates* that the cancel requirement makes sense independently from the others.

-Dave

···

On Dec 14, 2015, at 7:47 AM, Paul Cantrell <cantrell@pobox.com> wrote:

On Dec 14, 2015, at 1:37 AM, Dave Abrahams <dabrahams@apple.com <mailto:dabrahams@apple.com>> wrote:

func maxY(p1: P, p2: P) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

…right?

Ah, but then you have the situation that P doesn't conform to P (it doesn't have an x that you can access). In my opinion that is just too weird an idea to be usable.
Personally, I have always thought that protocols intended to be used as unbound existentials (not P<A: Int>, just plain P) are different beasts entirely from the kind used as generic constraints and should be explicitly declared as such—I don't think I've ever seen a protocol that is well-used in both ways; if you have counterexamples I'd love to see them. In "existential protocols," declaring an associated type or creating a self requirement would be an error at the point of declaration.

IMHO, it’s an artificial distinction that makes sense only if you’re acclimated to Swift’s current behavior.

There are plenty of situations where you really do want a generic type in the protocol, but you nonetheless find yourself in situations where you care only about the parts of the protocol that don’t depend on that generic type.

Here’s an example, pared down a lot but taken from an actual project:

    protocol Request
        {
        typealias ContentType
        
        func onCompletion(callback: (ContentType?, ErrorType?) -> Void)
        func onSuccess(callback: ContentType -> Void)
        func onNewData(callback: ContentType -> Void)
        func onFailure(callback: ErrorType -> Void)
        
        func cancel()
        }

    struct RequestBatch
        {
        var requests: [Request] // Sadness. Despair. DOOOOM.
        
        func cancelAll()
            {
            for request in requests
                { request.cancel() }
            }
        }

The current Swift solution is either to ditch the type safety of ContentType, or split Request into two protocols. The latter makes the API hard to read, and may decouple related protocol requirements that don’t make sense independently.


(Matthew Johnson) #12

It is definitely possible to solve this problem for associated types by binding or constraining them. The ML module system has a feature that is roughly analogous to this.

I think Self is quite a bit trickier, although there is effectively a implicit constraint that Self conforms to the protocol in question so it may be possible to make use of that information.

···

Sent from my iPad

On Dec 14, 2015, at 8:02 AM, Andrew Bennett via swift-evolution <swift-evolution@swift.org> wrote:

I agree that this is an issue, I'd really like to see a language feature to address it.

In some circumstances you can work around the issue with type-erasure. You implement a generic wrapper (like AnySequence) that can take anything conforming to that protocol and expose it with a single concrete interface.

I learnt a lot implementing a few of these. There's a good article on it here:
https://realm.io/news/type-erased-wrappers-in-swift/

I wouldn't be too surprised if Apple formalises type erasure somehow, the implementation is very mechanical and could be automated. For example: If you have a protocol MyProtocol that has type requirements then perhaps MyProtocol.Any would refer to an automatically generated type erased concrete type. I suppose you could also do:

typealias AB = protocol<A,B>
AB.Any

The main problem with current implementations seems to be, for example: ZipSequence is a SequenceType, but ZipSequence is not an AnySequence.

Also you cannot necessarily conform to a protocol if it has things like initialiser requirements.

On Tue, Dec 15, 2015 at 12:45 AM, Tal Atlas via swift-evolution <swift-evolution@swift.org> wrote:
This is the error that gets in my way the most when trying to do protocol oriented programming. It can be super frustrating. That lead me to make a proposal a few weeks back for having generic protocols.

In that proposal some swift core people expressed that they were keenly aware of said pain. And while they didn't deliver details in time or implementation they expressed that there were efforts ongoing to solve it.

On Sun, Dec 13, 2015 at 6:55 PM Marc Knaup via swift-evolution <swift-evolution@swift.org> wrote:
Hey guys,

I'm looking at Swift 3.0's goal to improve generics.

Is there any info yet if and how we will be able to refer to instances of protocols that have associated types?
What is the difficulty in supporting this?

Simple examples:
var list = [Hashable]()
var hashable: Hashable = 2

Right now all we get is

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

Thanks,
  Marc

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

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

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


(Paul Cantrell) #13

func maxY(p1: P, p2: P) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

…right?

Ah, but then you have the situation that P doesn't conform to P (it doesn't have an x that you can access). In my opinion that is just too weird an idea to be usable.
Personally, I have always thought that protocols intended to be used as unbound existentials (not P<A: Int>, just plain P) are different beasts entirely from the kind used as generic constraints and should be explicitly declared as such—I don't think I've ever seen a protocol that is well-used in both ways; if you have counterexamples I'd love to see them. In "existential protocols," declaring an associated type or creating a self requirement would be an error at the point of declaration.

IMHO, it’s an artificial distinction that makes sense only if you’re acclimated to Swift’s current behavior.

There are plenty of situations where you really do want a generic type in the protocol, but you nonetheless find yourself in situations where you care only about the parts of the protocol that don’t depend on that generic type.

Here’s an example, pared down a lot but taken from an actual project:

    protocol Request
        {
        typealias ContentType
        
        func onCompletion(callback: (ContentType?, ErrorType?) -> Void)
        func onSuccess(callback: ContentType -> Void)
        func onNewData(callback: ContentType -> Void)
        func onFailure(callback: ErrorType -> Void)
        
        func cancel()
        }

    struct RequestBatch
        {
        var requests: [Request] // Sadness. Despair. DOOOOM.
        
        func cancelAll()
            {
            for request in requests
                { request.cancel() }
            }
        }

The current Swift solution is either to ditch the type safety of ContentType, or split Request into two protocols. The latter makes the API hard to read, and may decouple related protocol requirements that don’t make sense independently.

You can achieve the same result more cleanly with

func maxY<T:P, U:P>(p1:T, p2: U) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

True, but this doesn’t generalize to other contexts, e.g. RequestBatch.requests above.

Conceptually, at least to me, a protocol describes a set of related capabilities that together add up to a meaningful behavior. Whether some of those capabilities share a generic type is incidental, and does not fundamentally change the nature of a protocol. Protocols should be protocols.

Java can do this via the <?> syntax:

    interface Request<ContentType>
        {
        void onCompletion(CompletionCallback<ContentType> callback);
        void onSuccess(SuccessCallback<ContentType> callback);
        void onNewData(SuccessCallback<ContentType> callback);
        void onFailure(FailureCallback<ContentType> callback);
        
        void cancel();
        }

    class RequestBatch
        {
        private List<Request<?>> requests;
        
        public void cancelAll()
            {
            for(Request<?> request: requests)
                { request.cancel(); }
            }
        }

And yes, “Java can do it” is a deliberate provocation! :stuck_out_tongue:

Cheers,

Paul

–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
https://innig.net@inthehandshttp://siestaframework.com/

···

On Dec 14, 2015, at 1:37 AM, Dave Abrahams <dabrahams@apple.com> wrote:
On Dec 14, 2015, at 1:40 AM, ilya <ilya.nikokoshev@gmail.com> wrote:


(Joe Groff) #14

With generic typealiases, you could say something like this:

typealias AnySequence<Element> =
  protocol<SequenceType where Generator.Element == Element>

However, once we add 'where' requirements to protocols, it seems to me 'Element' really ought to be a direct requirement of SequenceType, like this:

protocol SequenceType {
  typealias Element
  typealias Generator: GeneratorType
    where Element == Generator.Element
}

which would save generic code from having to splatter '.Generator.Element' everywhere, and make unspecialized code more efficient, since the type metadata for Element would be directly available from the SequenceType protocol witness table instead of needing an extra indirection through to its Generator's GeneratorType table.

-Joe

···

On Dec 14, 2015, at 7:40 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 14, 2015, at 6:23 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

It is definitely possible to solve this problem for associated types by binding or constraining them. The ML module system has a feature that is roughly analogous to this.

Keep in mind that a language feature like binding/constraining associated types is not a complete solution for real use-cases, so we would still need AnySequence<Element> in the library. Otherwise it would be SequenceType<Generator: SomeGenerator>, which binds a type that you don't actually want to expose.


(Matthew Johnson) #15

It is definitely possible to solve this problem for associated types by binding or constraining them. The ML module system has a feature that is roughly analogous to this.

Keep in mind that a language feature like binding/constraining associated types is not a complete solution for real use-cases, so we would still need AnySequence<Element> in the library. Otherwise it would be SequenceType<Generator: SomeGenerator>, which binds a type that you don't actually want to expose.

Wouldn’t we get a lot of utility by binding / constraining Generator.Element? Something like:

protocol<SequenceType where Generator.Element == Int>
or
protocol<SequenceType where Generator.Element: Equatable>

In that case we don’t know the specific binding of SequenceType’s Generator but we do know what constraints or binding its Element must have.

Matthew


(Paul Cantrell) #16

…and therein lies the danger of small examples trimmer from larger projects. In the actual project, I end up with a family of related behaviors where the “cancel / everything else” separation no longer makes sense.

Here’s a little more context — still a super pared down toy example, but hopefully enough to demonstrate. What I want:

    protocol Resource
        {
        typealias ContentType
        
        var allRequests: [Request<ContentType>] { get }
        var latestData: ContentType? { get }
        func load() -> Request<ContentType>
        }

    protocol Request
        {
        typealias ContentType
        
        func onSuccess(callback: ContentType -> Void)
        func onFailure(callback: ErrorType -> Void)
        func cancel()
        }

    struct ResourceBatch
        {
        var observedResources: [Resource]
        var reloadsInProgress: Int = 0
        
        func reloadAll()
            {
            for resource in observedResources
                {
                reloadsInProgress += 1
                resource.load().onSuccess
                    { _ in self.reloadsInProgress -= 1 }
                }
            }
        
        func cancelAll()
            {
            for resource in observedResources
                {
                for request in resource.allRequests
                    { request.cancel() }
                }
            }
        }

And here’s the nonsense I have to engage in to make this work with Swift as it stands:

    protocol AnyResource
        {
        var allRequests: [AnyRequest] { get }
        var anyLatestData: Any? { get }
        func load() -> AnyRequest
        }

    protocol Resource: AnyResource
        {
        typealias ContentType
        
        var allRequests: [AnyRequest] { get } // ← Still no way to make this typesafe
        var latestData: ContentType? { get }
        
        func load<RequestType: Request where RequestType.ContentType == Self.ContentType>() -> RequestType
        }

    protocol AnyRequest
        {
        func onSuccess(callback: Any -> Void)
        func onFailure(callback: ErrorType -> Void)
        func cancel()
        }

    protocol Request: AnyRequest
        {
        typealias ContentType
        
        func onSuccess(callback: ContentType -> Void)
        }

    struct ResourceBatch
        {
        var observedResources: [AnyResource]
        var reloadsInProgress: Int = 0
        
        mutating func reloadAll()
            {
            for resource in observedResources
                {
                reloadsInProgress += 1
                resource.load().onSuccess
                    { _ in self.reloadsInProgress -= 1 }
                }
            }
        
        func cancelAll()
            {
            for resource in observedResources
                {
                for request in resource.allRequests
                    { request.cancel() }
                }
            }
        }

Still just a sketch, but already the problems abound.

This Resource / AnyResource and Request / AnyRequest distinction makes no useful sense to the user of this library; it’s just there to satisfy Swift’s type system. “Resource whose ContentType is known at compile time” and “Resource whose ContentType is unknown at compile time” are still the same construct, conceptually and semantically, just with varying degrees of type information. They should not be two types.

I tried playing out this approach in Siesta, and it lead to a combinatorial explosion of duplicated methods, and numerous type inference problems related to the collision of methods that differed only by Any vs. ContentType. I wasn’t willing to inflict the resulting API on my users.

Maybe I’m missing something big. David, you certainly know Swift better than I do. If there’s a better way to do this, I would love to hear about it!

Cheers,

Paul

···

On Dec 14, 2015, at 10:01 AM, Dave Abrahams <dabrahams@apple.com> wrote:

    struct RequestBatch
        {
        var requests: [Request] // Sadness. Despair. DOOOOM.
        
        func cancelAll()
            {
            for request in requests
                { request.cancel() }
            }
        }

The current Swift solution is either to ditch the type safety of ContentType, or split Request into two protocols. The latter makes the API hard to read, and may decouple related protocol requirements that don’t make sense independently.

IMO the fact that you created an a array of requests and wrote cancelAll above *demonstrates* that the cancel requirement makes sense independently from the others.


(Marc Knaup) #17

A protocol X could fulfill conformance to a protocol Y.

protocol SomeProtocol: Equatable {}
enum SomeType1: SomeProtocol { case Case }
enum SomeType2: SomeProtocol { case Case }

func == (a: SomeProtocol, b: SomeProtocol) -> Bool { // should be allowed
to use protocol directly
  switch (a, b) {
  case let (a, b) as (SomeType1, SomeType1): return a == b
  case let (a, b) as (SomeType2, SomeType2): return a == b
  default: return false
  }
}

// Equatable requirement is fulfilled for SomeProtocol
let a: SomeProtocol = SomeType1.Case // should be allowed to use protocol
directly
let b: SomeProtocol = SomeType2.Case
print(a == b) // should print "false"

var array = [SomeProtocol]() // should be allowed to use protocol directly

That kind of conformance fulfillment should not be inherited though or else
this could cause a recursion, i.e. types implementing the protocol must
still conform to

Since SomeProtocol has internal visibility all types and protocols
explicitly declaring conformance are even known at compile-time.
For that reason implementing the equality operator for the protocol
wouldn't even be necessary. The compiler does already know that all types
conforming to the protocol are equatable and could even generate the
equality operator.

I find the suggestion from Paul Cantrell very useful where you can use all
properties and methods of the protocol which do not depend on an associated
type or Self.

var array = [Hashable]()
// …
for element in array {
    print(element.hashValue)
}

Who cares that it doesn't fulfill Equatable conformance yet?

···

On Mon, Dec 14, 2015 at 8:40 AM, ilya via swift-evolution < swift-evolution@swift.org> wrote:

You can achieve the same result more cleanly with

func maxY<T:P, U:P>(p1:T, p2: U) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

On Mon, Dec 14, 2015 at 10:03 AM, Paul Cantrell via swift-evolution < > swift-evolution@swift.org> wrote:

On Dec 13, 2015, at 11:56 PM, Dave Abrahams via swift-evolution < >> swift-evolution@swift.org> wrote:

On Dec 13, 2015, at 3:55 PM, Marc Knaup via swift-evolution < >> swift-evolution@swift.org> wrote:

Is there any info yet if and how we will be able to refer to instances of
protocols that have associated types?

As far as I know, this isn't a solvable problem.

What is the difficulty in supporting this?

Here's a simple example:

protocol P {
  typealias A
  var x: A { get set }
}

struct Y : P {
  var x: Int
}

struct Z : P {
  var x: String
}

func f(p1: P, p2: P) {
  p1.x = p2.x // assigning an Int to a String?
}

p1.x = p2.x should be a compiler error, because there’s not enough type
information. But that shouldn’t stop a programmer from doing this:

protocol P {
  typealias A
  var x: A { get set }
  var y: Int
}

struct Y : P {
  var x: Int
  var y: Int
}

struct Z : P {
  var x: String
  var y: Int
}

func maxY(p1: P, p2: P) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

…right?

Cheers, P

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

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


(Marc Knaup) #18

@Andrew sure you could provide an init implementation for a protocol. It
would be like a factory for that protocol similar to class clusters in
Objective-C.
Actually a proposal I'm working on depends on that being possible somehow :slight_smile:

···

On Mon, Dec 14, 2015 at 3:02 PM, Andrew Bennett <cacoyi@gmail.com> wrote:

I agree that this is an issue, I'd really like to see a language feature
to address it.

In some circumstances you can work around the issue with type-erasure. You
implement a generic wrapper (like AnySequence) that can take anything
conforming to that protocol and expose it with a single concrete interface.

I learnt a lot implementing a few of these. There's a good article on it
here:
https://realm.io/news/type-erased-wrappers-in-swift/

I wouldn't be too surprised if Apple formalises type erasure somehow, the
implementation is very mechanical and could be automated. For example: If
you have a protocol MyProtocol that has type requirements then perhaps
MyProtocol.Any would refer to an automatically generated type erased
concrete type. I suppose you could also do:

typealias AB = protocol<A,B>
AB.Any

The main problem with current implementations seems to be, for example:
ZipSequence is a SequenceType, but ZipSequence is not an AnySequence.

Also you cannot necessarily conform to a protocol if it has things like
initialiser requirements.

On Tue, Dec 15, 2015 at 12:45 AM, Tal Atlas via swift-evolution < > swift-evolution@swift.org> wrote:

This is the error that gets in my way the most when trying to do protocol
oriented programming. It can be super frustrating. That lead me to make a
proposal a few weeks back for having generic protocols.

In that proposal some swift core people expressed that they were keenly
aware of said pain. And while they didn't deliver details in time or
implementation they expressed that there were efforts ongoing to solve it.

On Sun, Dec 13, 2015 at 6:55 PM Marc Knaup via swift-evolution < >> swift-evolution@swift.org> wrote:

Hey guys,

I'm looking at Swift 3.0's goal to improve generics.

Is there any info yet if and how we will be able to refer to instances
of protocols that have associated types?
What is the difficulty in supporting this?

Simple examples:
var list = [Hashable]()
var hashable: Hashable = 2

Right now all we get is

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

Thanks,
  Marc

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

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


(Marc Knaup) #19

Isn't this issue related to what Kotlin tries to solve with in/out
variances (and star-projection)?
https://kotlinlang.org/docs/reference/generics.html

Btw. what was the reason again that protocols use a typealias syntax
instead of "protocol Protocol<T>"?

···

On Mon, Dec 14, 2015 at 4:47 PM, Paul Cantrell via swift-evolution < swift-evolution@swift.org> wrote:

On Dec 14, 2015, at 1:37 AM, Dave Abrahams <dabrahams@apple.com> wrote:

func maxY(p1: P, p2: P) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

…right?

Ah, but then you have the situation that P doesn't conform to P (it
doesn't have an x that you can access). In my opinion that is just too
weird an idea to be usable.
Personally, I have always thought that protocols intended to be used as
unbound existentials (not P<A: Int>, just plain P) are different beasts
entirely from the kind used as generic constraints and should be explicitly
declared as such—I don't think I've ever seen a protocol that is well-used
in both ways; if you have counterexamples I'd love to see them. In
"existential protocols," declaring an associated type or creating a self
requirement would be an error at the point of declaration.

IMHO, it’s an artificial distinction that makes sense only if you’re
acclimated to Swift’s current behavior.

There are plenty of situations where you really do want a generic type in
the protocol, but you nonetheless find yourself in situations where you
care only about the parts of the protocol that don’t depend on that generic
type.

Here’s an example, pared down a lot but taken from an actual project:

    protocol Request
        {
        typealias ContentType

        func onCompletion(callback: (ContentType?, ErrorType?) -> Void)
        func onSuccess(callback: ContentType -> Void)
        func onNewData(callback: ContentType -> Void)

        func onFailure(callback: ErrorType -> Void)

        func cancel()
        }

    struct RequestBatch
        {
        var requests: [Request] // Sadness. Despair. DOOOOM.

        func cancelAll()
            {
            for request in requests
                { request.cancel() }
            }
        }

The current Swift solution is either to ditch the type safety of
ContentType, or split Request into two protocols. The latter makes the API
hard to read, and may decouple related protocol requirements that don’t
make sense independently.

On Dec 14, 2015, at 1:40 AM, ilya <ilya.nikokoshev@gmail.com> wrote:

You can achieve the same result more cleanly with

func maxY<T:P, U:P>(p1:T, p2: U) -> Int {
  return max(p1.y, p2.y) // No problems here!
}

True, but this doesn’t generalize to other contexts, e.g.
RequestBatch.requests above.

Conceptually, at least to me, a protocol describes a set of related
capabilities that together add up to a meaningful behavior. Whether some of
those capabilities share a generic type is incidental, and does not
fundamentally change the nature of a protocol. Protocols should be
protocols.

Java can do this via the <?> syntax:

    interface Request<ContentType>
        {
        void onCompletion(CompletionCallback<ContentType> callback);
        void onSuccess(SuccessCallback<ContentType> callback);
        void onNewData(SuccessCallback<ContentType> callback);
        void onFailure(FailureCallback<ContentType> callback);

        void cancel();
        }

    class RequestBatch
        {
        private List<Request<?>> requests;

        public void cancelAll()
            {
            for(Request<?> request: requests)
                { request.cancel(); }
            }
        }

And yes, “Java can do it” is a deliberate provocation! :stuck_out_tongue:

Cheers,

Paul

–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
https://innig.net@inthehandshttp://siestaframework.com/

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


(Thorsten Seitz) #20

Shouldn’t we just narrow the type of protocols with associated types and/or Self types when the protocol is used as a type?

// (1) Associated types

protocol Bounds {
    func doSomething()
}

protocol Foo {
    
    // associated type with bounds
    typealias T where T: Bounds

    // getter is covariant, setter is contravariant
    var value: T { get set }
    
    // result is covariant
    func foo() -> T
    
    // arg is contravariant
    func bar(arg: T)
    func baz(arg: T) -> T
    
    // arg of arg is covariant
    func f(g: T -> Void)
}

var foo: Foo

// Covariant places of associated type T
// are ok and use bounds of T
// (i.e. Any if no bounds are given).
// In our example the bounds are "Bounds"
let x: Bounds = foo.value
let y: Bounds = foo.foo()
foo.f { (z: Bounds) in z.doSomething() }

// Contravariant places of associated type T
// are forbidden, i.e. methods containing T
// as argument or setters of variables of type T
// are not allowed. In effect they are not part
// of the type Foo.
foo.value = x // ERROR: calling setter not allowed
foo.bar(x) // ERROR: not allowed
let z: Bounds = foo.baz(x) // ERROR: not allowed

// (2) Self types

protocol Bar {
    
    var value: Self { get set }
    func foo() -> Self
    func bar(arg: Self)
    func baz(arg: Self) -> Self
    func f(g: Self -> Void)
}

// Same as for associated types, just
// that the bounds for Self are the protocol
// itself, i.e. in this example: Bar

// (3) Constraining protocols with associated types or self types

protocol Child : Bounds {}

let foo2: Foo where T: Child // would work like above with narrower bounds Child
let foo3: Foo where T == Child // would make all methods and setters available with T == Child

// (4) Equatable and Set

// Equatable with associated type EquatableWith like suggested by Joe Groff
public protocol Equatable {
    typealias EquatesWith = Self where Self: EquatesWith
    func ==(lhs: EquatesWith, rhs: EquatesWith) -> Bool
}

protocol P : Hashable {
    typealias EquatesWith = P // fixing the associated type of Equatable
    typealias T where T: Bounds
    func foo() -> T
    var id: Int
}

func ==(lhs: P, rhs: P) -> Bool {
    return lhs.id == rhs.id // accessing id is ok
}

var p1: P
var p2: P
if p1 == p2 { // ok, because EquatableWith has been fixed to be of type P
    print("Ok!")
}

var ps: Set<P> // now ok, because == can be used on variables of type p
ps.insert(p1)
ps.insert(p2)
var bs: [Bounds] = ps.map { $0.foo() } // ok

With the proposed change of
-Thorsten

···

Am 14.12.2015 um 19:32 schrieb Joe Groff via swift-evolution <swift-evolution@swift.org>:

On Dec 14, 2015, at 7:40 AM, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Dec 14, 2015, at 6:23 AM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

It is definitely possible to solve this problem for associated types by binding or constraining them. The ML module system has a feature that is roughly analogous to this.

Keep in mind that a language feature like binding/constraining associated types is not a complete solution for real use-cases, so we would still need AnySequence<Element> in the library. Otherwise it would be SequenceType<Generator: SomeGenerator>, which binds a type that you don't actually want to expose.

With generic typealiases, you could say something like this:

typealias AnySequence<Element> =
  protocol<SequenceType where Generator.Element == Element>

However, once we add 'where' requirements to protocols, it seems to me 'Element' really ought to be a direct requirement of SequenceType, like this:

protocol SequenceType {
  typealias Element
  typealias Generator: GeneratorType
    where Element == Generator.Element
}

which would save generic code from having to splatter '.Generator.Element' everywhere, and make unspecialized code more efficient, since the type metadata for Element would be directly available from the SequenceType protocol witness table instead of needing an extra indirection through to its Generator's GeneratorType table.

-Joe

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