On Sun, Jun 12, 2016 at 8:01 AM, Антон Жилин <swift-evolution@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 <br...@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@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