[swift-evolution] [Draft] Allow multiple conformances to the same protocol

Dan Appel dan.appel00 at gmail.com
Sun Jun 12 13:51:26 CDT 2016


Awesome that you guys started this! I've been meaning to pitch it for a
while. Couple notes:

- in the motivation, you're conforming to SequenceType while defining a
>From protocol (just a typo I think)
- the proposal should mention Brent's comments since it doesn't have a
rebuttal to the "unlikely" status generic protocols were given in the
generic manifesto. Brent does a great job of doing that
- is there a way for generic protocols to still have associated types? I
think that could have some interesting use cases.

Dan Appel

On Sun, Jun 12, 2016 at 11:28 AM Антон Жилин <swift-evolution at swift.org>
wrote:

> Yes, everything that works on generic types should work for generic
> protocols. I'll add that.
>
> What won't work is declaring that  MyComparable : Comparable  iff  T ==
> Self. The same won't work for current non-generic protocols as well.
> Although that feature is highly requested, it is discussed in a separate
> proposal.
>
> - Anton
>
> 2016-06-12 21:16 GMT+03:00 Xiaodi Wu <xiaodi.wu at gmail.com>:
>
>>
>>
>> On Sun, Jun 12, 2016 at 8:01 AM, Антон Жилин <swift-evolution at swift.org>
>> wrote:
>>
>>> I've prepared a proper draft:
>>>
>>>
>>> https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md
>>>
>>>
>> When you propose this:
>> Syntax in protocol extensions
>>
>> protocol MyComparable<T> {
>>   func < (left: Self, right: T)
>> }extension MyComparable {
>>   func > (left: T, right: Self) {
>>     return right < left
>>   }
>> }
>>
>>
>> Would it be possible for me to write something like:
>>
>> ```
>> extension MyComparable<T : SignedNumber> { ... }
>> ```
>>
>>
>> - Anton
>>>
>>> 2016-06-10 17:18 GMT+03:00 Brent Royal-Gordon <brent at architechies.com>:
>>>
>>>> > FWIW they're marked as 'unlikely' here:
>>>> https://github.com/apple/swift/blob/master/docs/GenericsManifesto.md#generic-protocols
>>>> >
>>>> > It would probably be useful to have counterarguments against the
>>>> points raised in that document if you want to prepare a proposal.
>>>>
>>>> Here's my counterargument.
>>>>
>>>>         * * *
>>>>
>>>> Firstly, I think they're underestimating the feature's utility. Generic
>>>> protocols (real generic protocols, not Sequence<Element>) are already
>>>> needed to make several existing or likely future features work better. For
>>>> instance:
>>>>
>>>> * Pattern matching
>>>>
>>>> Currently, if you want to customize your type's behavior in a `switch`
>>>> statement, you do it in an ad hoc, almost Objective-C-like way: You define
>>>> a free `~=` operator and the compiler resolves the overloads to magically
>>>> find and use it. There is no way to constrain a generic parameter to "only
>>>> types that can pattern match against type X", which seems like a pretty
>>>> useful thing to offer. For instance, in the past people have suggested some
>>>> sort of expression-based switch alternative. The lack of a pattern matching
>>>> protocol makes this impossible to implement in either the standard library
>>>> or your own code.
>>>>
>>>> If we had generic protocols, we could define a protocol for this
>>>> matching operator and fix the issue:
>>>>
>>>>         protocol Matchable<MatchingValue> {
>>>>                 func ~= (pattern: Self, value: MatchingValue) -> Bool
>>>>         }
>>>>
>>>>         protocol Equatable: Matchable<Self> {
>>>>                 func == (lhs: Self, rhs: Self) -> Bool
>>>>         }
>>>>         func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
>>>>                 return lhs == rhs
>>>>         }
>>>>
>>>>         extension Range: Equatable, Matchable<Bound> {}
>>>>         func ~= <Bound: Comparable>(pattern: Range<Bound>, value:
>>>> Bound) -> Bool {
>>>>                 return pattern.lowerBound <= value && value <
>>>> pattern.upperBound
>>>>         }
>>>>
>>>> Then you could write, for instance, a PatternDictionary which took
>>>> patterns instead of keys and, when subscripted, matched the key against
>>>> each pattern until it found a matching one, then returned the corresponding
>>>> value.
>>>>
>>>> * String interpolation
>>>>
>>>> Currently, StringInterpolationConvertible only offers an
>>>> `init<T>(stringInterpolationSegment: T)` initializer. That means you
>>>> absolutely *must* permit any type to be interpolated into your type's
>>>> string literals. This blocks certain important use cases, like a
>>>> `LocalizedString` type which requires all strings it interacts with to pass
>>>> through a localization API, from being statically checked. It also would
>>>> normally require any type-specific behavior to be performed through runtime
>>>> tests, but just as in `~=`, the Swift compiler applies compile-time magic
>>>> to escape this restriction—you can write an
>>>> `init(stringInterpolationSegment:)` with a concrete type, and that will be
>>>> preferred over the generic one.
>>>>
>>>> In theory, it should be possible in current Swift to redefine
>>>> StringInterpolationConvertible to allow you to restrict the interpolatable
>>>> values by doing something like this:
>>>>
>>>>         protocol StringInterpolationConvertible {
>>>>                 associatedtype Interpolatable = Any
>>>>                 init(stringInterpolation: Self...)
>>>>                 init(stringInterpolationSegment expr: Interpolatable)
>>>>         }
>>>>
>>>> (This is no longer generic because I believe Interpolatable would have
>>>> to be somehow constrained to only protocol types to make that work. But you
>>>> get the idea.)
>>>>
>>>> However, in many uses, developers will want to support interpolation of
>>>> many custom types which do not share a common supertype. For instance,
>>>> LocalizedString might want to support interpolation of any LocalizedString,
>>>> Date, Integer, or FloatingPoint number. However, since Integer and
>>>> FloatingPoint are protocols, you cannot use an extension to make them
>>>> retroactively conform to a common protocol with LocalizedString.
>>>>
>>>> With generic protocols, we could define StringInterpolationConvertible
>>>> like this:
>>>>
>>>>         protocol StringInterpolationConvertible<Interpolatable> {
>>>>                 init(stringInterpolation: Self...)
>>>>                 init(stringInterpolationSegment expr: Interpolatable)
>>>>         }
>>>>
>>>> And then say:
>>>>
>>>>         extension LocalizedString:
>>>> StringInterpolationConvertible<LocalizedString>,
>>>> StringInterpolationConvertible<Integer>,
>>>> StringInterpolationConvertible<FloatingPoint> {
>>>>                 init(stringInterpolationSegment expr: LocalizedString) {
>>>>                         self.init()
>>>>                         self.components = expr.components
>>>>                 }
>>>>                 init(stringInterpolationSegment expr: Integer) {
>>>>                         self.init()
>>>>                         self.components.append(.integer(expr))
>>>>                 }
>>>>                 init(stringInterpolationSegment expr: FloatingPoint) {
>>>>                         self.components.append(.floatingPoint(expr))
>>>>                 }
>>>>                 init(stringInterpolation strings: LocalizedString...) {
>>>>                         self.init()
>>>>                         self.components = strings.map { $0.components
>>>> }.reduce([], combine: +)
>>>>                 }
>>>>         }
>>>>
>>>> This example shows an interesting wrinkle: A generic protocol may have
>>>> requirements which don't use any of the generic types, so that each of the
>>>> multiple conformances will require members with identical signatures. When
>>>> this happens, Swift must only allow the member to be implemented once, with
>>>> that implementation being shared among all conformances.
>>>>
>>>> * Subtype-supertype relationships
>>>>
>>>> Though not currently implemented, there are long-term plans to permit
>>>> at least value types to form subtype-supertype relationships with each
>>>> other. A protocol would be a sensible way to express this behavior:
>>>>
>>>>         protocol Upcastable {
>>>>                 associatedtype Supertype
>>>>
>>>>                 init?(attemptingCastFrom value: Supertype)
>>>>                 func casting() -> Supertype
>>>>         }
>>>>
>>>> However, this would require a type to have only one supertype, which
>>>> isn't necessarily appropriate. For instance, we might want a UInt8 to be a
>>>> subtype of both Int16 and UInt16. For that to work, Upcastable would have
>>>> to be generic:
>>>>
>>>>         protocol Upcastable<Supertype> {
>>>>                 init?(attemptingCastFrom value: Supertype)
>>>>                 func casting() -> Supertype
>>>>         }
>>>>
>>>>         extension UInt8: Upcastable<Int16>, Upcastable<UInt16> { … }
>>>>
>>>> Without generic protocols, the only way to offer sufficiently flexible
>>>> subtyping is to offer it as a one-off, ad-hoc feature with special syntax.
>>>>
>>>>         * * *
>>>>
>>>> Secondly, I think the concerns about people trying to use Sequence as a
>>>> generic protocol aren't that big a deal. To put it simply: Sequence is
>>>> *not* a generic protocol. The Swift team controls the definition of
>>>> Sequence, and we define it to not be generic. If people complain, we
>>>> explain that generic protocols don't actually do the right thing for this
>>>> and that they should use existentials instead. We put it in a FAQ. It's
>>>> just not that big a deal.
>>>>
>>>> The real concern is not that people will try to use Sequence as a
>>>> generic protocol, but that they will try to inappropriately make their own
>>>> protocols generic. I see this as a more minor issue, but if we're worried
>>>> about it, we can address it by changing the mental model to one which
>>>> doesn't make it look like a generics feature.
>>>>
>>>> Basically, rather than thinking of this feature as "generic protocols",
>>>> it could instead be thought of as "associated type overloading": a
>>>> particular associated type can be overloaded, and you can use a `where`
>>>> clause to select a particular overload. This would have a different syntax
>>>> but handle the same use cases.
>>>>
>>>> For instance, rather than saying this:
>>>>
>>>>         protocol Matchable<MatchingValue> {
>>>>                 func ~= (pattern: Self, value: MatchingValue) -> Bool
>>>>         }
>>>>
>>>>         protocol Equatable: Matchable<Self> {
>>>>                 func == (lhs: Self, rhs: Self) -> Bool
>>>>         }
>>>>         func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
>>>>                 return lhs == rhs
>>>>         }
>>>>
>>>>         extension Range: Equatable, Matchable<Bound> {}
>>>>         func ~= <Bound: Comparable>(pattern: Range<Bound>, value:
>>>> Bound) -> Bool {
>>>>                 return pattern.lowerBound <= value && value <
>>>> pattern.upperBound
>>>>         }
>>>>
>>>>         struct PatternDictionary<Matching, Value>:
>>>> DictionaryLiteralConvertible {
>>>>                 typealias Key = Matchable<Matching>
>>>>                 typealias Value = OutValue
>>>>
>>>>                 var patterns: DictionaryLiteral<Key, Value>
>>>>                 init(dictionaryLiteral pairs: (Key, Value)...) {
>>>> patterns = DictionaryLiteral(pairs) }
>>>>
>>>>                 subscript(matchingValue: Matching) -> Value? {
>>>>                         for (pattern, value) in patterns {
>>>>                                 if pattern ~= matchingValue {
>>>>                                         return value
>>>>                                 }
>>>>                         }
>>>>                         return nil
>>>>                 }
>>>>         }
>>>>
>>>> You could instead say:
>>>>
>>>>         protocol Matchable {
>>>>                 @overloadable associatedtype MatchingValue
>>>>                 func ~= (pattern: Self, value: MatchingValue) -> Bool
>>>>         }
>>>>
>>>>         protocol Equatable: Matchable where MatchingValue |= Self {
>>>>                 func == (lhs: Self, rhs: Self) -> Bool
>>>>         }
>>>>         func ~= <T: Equatable>(lhs: T, rhs: T) -> Bool {
>>>>                 return lhs == rhs
>>>>         }
>>>>
>>>>         extension Range: Equatable, Matchable {
>>>>                 typealias MatchingValue |= Bound
>>>>         }
>>>>         func ~= <Bound: Comparable>(pattern: Range<Bound>, value:
>>>> Bound) -> Bool {
>>>>                 return pattern.lowerBound <= value && value <
>>>> pattern.upperBound
>>>>         }
>>>>
>>>>         struct PatternDictionary<Matching, Value>:
>>>> DictionaryLiteralConvertible {
>>>>                 typealias Key = Any<Matchable where .MatchingValue &
>>>> Matching>
>>>>                 typealias Value = Value
>>>>
>>>>                 var patterns: DictionaryLiteral<Key, Value>
>>>>                 init(dictionaryLiteral pairs: (Key, Value)...) {
>>>> patterns = DictionaryLiteral(pairs) }
>>>>
>>>>                 subscript(matchingValue: Matching) -> Value? {
>>>>                         for (pattern, value) in patterns {
>>>>                                 if pattern ~= matchingValue {
>>>>                                         return value
>>>>                                 }
>>>>                         }
>>>>                         return nil
>>>>                 }
>>>>         }
>>>>
>>>> (Is `MatchingValue |= Bound` a union type feature? I'm not sure. It
>>>> does have the syntax of one, but there's a separate overload for each type,
>>>> so I don't think it really acts like one.)
>>>>
>>>> This is very nearly the same feature, but presented with different
>>>> syntax—effectively with a different metaphor. That should prevent it from
>>>> being abused the way the core team fears it will be.
>>>>
>>>> (One difference is that this version permits "vacuous" conformances: in
>>>> theory, there's no reason you couldn't conform to a protocol with an
>>>> `@overloadable associatedtype` and define zero types. On the other hand,
>>>> that's not necessarily *wrong*, and might even be useful in some cases.)
>>>>
>>>> --
>>>> Brent Royal-Gordon
>>>> Architechies
>>>>
>>>>
>>>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>>
>>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
-- 
Dan Appel
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160612/f61587dd/attachment.html>


More information about the swift-evolution mailing list