[swift-evolution] protocol can only be used as a generic constraint because it has Self or associated type requirements

Paul Cantrell cantrell at pobox.com
Mon Dec 14 09:47:14 CST 2015


> On Dec 14, 2015, at 1:37 AM, Dave Abrahams <dabrahams at 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 at 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! :P

Cheers,

Paul

–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
https://innig.net • @inthehands • http://siestaframework.com/

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151214/d9e8e172/attachment.html>


More information about the swift-evolution mailing list