+1

Sent from my iPhone

> On Jun 12, 2016, at 08:01, Антон Жилин via swift-evolution 
> <[email protected]> wrote:
> 
> I've prepared a proper draft:
> 
> https://github.com/Anton3/swift-evolution/blob/generic-protocols/proposals/NNNN-generic-protocols.md
> 
> - Anton
> 
> 2016-06-10 17:18 GMT+03:00 Brent Royal-Gordon <[email protected]>:
>> > 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
> [email protected]
> https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to