<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">Below I’ve provided a more fleshed out version of what Dave is suggesting, for anyone who had trouble parsing the very hypothetical example. It reflects the kind of implementation specialization I would expect to see in the standard library. In fact we have exactly this concern baked into value witness tables today by differentiating the various *Buffer methods so that they can be no-ops or memcpys for trivial values (rather than requiring code to loop over all the elements and call potentially-no-op methods on each element).</div><div class=""><br class=""></div><div class="">But the value witness table’s approach to this problem suggests some healthier solutions to the problem (for Swift’s particular set of constraints):</div><div class=""><br class=""></div><div class="">1) Add default-implemented fullAlgorithm() methods to the protocol. Anyone who can beat the default algorithm provides their own implementation. Consumers of the protocol then dispatch to fullAlgorithm(), rather than the lower-level primitives.</div><div class=""><br class=""></div><div class="">2) Add “let hasInterestingProperty: bool” flags to the protocol. Consumers of the protocol can then branch on these flags to choose a “slow generic” or “fast specific” implementation. (this is, after all, exactly what we’re asking the runtime to do for us!)</div><div class=""><br class=""></div><div class="">Of course, (1) and (2) aren’t always applicable solutions. Both only <i class="">really</i> apply if you’re the original creator of the protocol; otherwise no one will know about fullAlgorithm or hasInterestingProperty and be able to modify the default. It can also be really tedious to provide your own implementation of fullAlgorithm(), especially if everyone overloads it in the same way. These are, however, perfectly reasonable approaches if you’re just trying to specialize for a small, closed, set of types. Something like:</div><div class=""><br class=""></div><div class="">genericImpl()</div><div class="">stringImpl()</div><div class="">intImpl()</div><div class=""><br class=""></div><div class="">You can handle that pretty easily with extensions or super-protocols, I think.</div><div class=""><br class=""></div><div class="">I’m cautiously optimistic we can get pretty far before we really feel the need to introduce specialization like this. Although I’m used to handling this issue in a world of monomorphic generics; so I’m not sure if the performance characteristics of polymorphic generics will shift the balance to making specialization more urgent. Or perhaps the opposite — the runtime impact of specialization could be too high!</div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><div class=""><font face="Courier" class="">// Some kind of "super copy" operation</font></div><div class=""><font face="Courier" class="">public protocol Clone {</font></div><div class=""><font face="Courier" class=""> func clone() -> Self</font></div><div class=""><font face="Courier" class="">}</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// Can just memcpy instead of calling Clone</font></div><div class=""><font face="Courier" class="">public protocol TrivialClone: Clone { }</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// A terrible data structure</font></div><div class=""><font face="Courier" class="">public struct FakeArray<T> { let vals: (T, T, T) }</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// --------------------------------------------------</font></div><div class=""><font face="Courier" class="">// A dirty hack to get overlapping impls (specifically specialization)</font></div><div class=""><font face="Courier" class="">// through overlapping extensions.</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">internal protocol CloneImpl {</font></div><div class=""><font face="Courier" class=""> associatedtype TT: Clone</font></div><div class=""><font face="Courier" class="">}</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">extension CloneImpl {</font></div><div class=""><font face="Courier" class=""> static func clone(input: FakeArray<TT>) -> FakeArray<TT> {</font></div><div class=""><font face="Courier" class=""> // Have to manually invoke generic `clone` on each element</font></div><div class=""><font face="Courier" class=""> FakeArray(vals: (input.vals.0.clone(),</font></div><div class=""><font face="Courier" class=""> input.vals.1.clone(),</font></div><div class=""><font face="Courier" class=""> input.vals.2.clone()))</font></div><div class=""><font face="Courier" class=""> }</font></div><div class=""><font face="Courier" class="">}</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">extension CloneImpl where TT: TrivialClone {</font></div><div class=""><font face="Courier" class=""> static func clone(input: FakeArray<TT>) -> FakeArray<TT> {</font></div><div class=""><font face="Courier" class=""> // Can just copy the whole buffer at once (ideally a memcpy)</font></div><div class=""><font face="Courier" class=""> FakeArray(vals: input.vals)</font></div><div class=""><font face="Courier" class=""> }</font></div><div class=""><font face="Courier" class="">}</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// Inject our specialized Clone impl</font></div><div class=""><font face="Courier" class="">// (doesn't compile today because this is a conditional conformance)</font></div><div class=""><font face="Courier" class="">extension FakeArray: Clone where T: Clone {</font></div><div class=""><font face="Courier" class=""> // A dummy to get our overlapping extensions</font></div><div class=""><font face="Courier" class=""> // (doesn't compile today because we can't nest types in a generic type)</font></div><div class=""><font face="Courier" class=""> struct CloneImplProvider : CloneImpl {</font></div><div class=""><font face="Courier" class=""> typealias TT = T</font></div><div class=""><font face="Courier" class=""> }</font></div><div class=""><font face="Courier" class=""> </font></div><div class=""><font face="Courier" class=""> func clone() -> FakeArray {</font></div><div class=""><font face="Courier" class=""> CloneImplProvider.clone(input: self)</font></div><div class=""><font face="Courier" class=""> }</font></div><div class=""><font face="Courier" class="">}</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// -------------------------------------------------</font></div><div class=""><font face="Courier" class="">// Using Clone and the specialization</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// Some plain-old-data</font></div><div class=""><font face="Courier" class="">struct POD : TrivialClone {</font></div><div class=""><font face="Courier" class=""> func clone() -> POD { return self }</font></div><div class=""><font face="Courier" class="">}</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// Works with any Clone type</font></div><div class=""><font face="Courier" class="">func generic<T: Clone>(_ value: T) -> T {</font></div><div class=""><font face="Courier" class=""> return value.clone()</font></div><div class=""><font face="Courier" class="">}</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// Pass in a FakeArray that should use the fast specialization for Clone</font></div><div class=""><font face="Courier" class="">generic(FakeArray(vals: (POD(), POD(), POD())))</font></div></div><div class=""><br class=""></div><div class=""><br class=""></div><div class=""><br class=""></div><br class=""><div><blockquote type="cite" class=""><div class="">On Sep 30, 2016, at 11:18 PM, Dave Abrahams via swift-evolution <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:</div><br class="Apple-interchange-newline"><div class=""><div class=""><br class="">on Fri Sep 30 2016, Matthew Johnson <<a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a>> wrote:<br class=""><br class=""><blockquote type="cite" class=""><blockquote type="cite" class="">It’s a valid concern, and I’m sure it does come up in practice. Let’s create a small, self-contained example:<br class=""><br class="">protocol P {<br class=""> func f()<br class="">}<br class=""><br class="">protocol Q: P { }<br class=""><br class="">struct X<T> { let t: T}<br class=""><br class="">extension X: P where T: P {<br class=""> func f() {<br class=""> /* general but slow */<br class=""> }<br class="">}<br class=""><br class="">extension X where T: Q {<br class=""> func f() {<br class=""> /* fast because it takes advantage of T: Q */<br class=""> }<br class="">}<br class=""><br class="">struct IsQ : Q { }<br class=""><br class="">func generic<U: P>(_ value: u) {<br class=""> value.f()<br class="">}<br class=""><br class="">generic(X<IsQ>())<br class=""><br class="">We’d like for the call to “value.f()” to get the fast version of f()<br class="">from the second extension, but the proposal doesn’t do that: the<br class="">conformance to P is “locked in” to the first extension.<br class=""></blockquote></blockquote><br class="">I suppose that's true even if the second extension introduces X : Q?<br class=""><br class=""><blockquote type="cite" class=""><blockquote type="cite" class="">If we assume that we can see all of the potential implementations of<br class="">“f” to which we might want to dispatch, we could implement some<br class="">dynamic scheme that tries to pick the most specialized one. Of<br class="">course, as with overlapping conformances in general, this selection<br class="">process could result in ambiguities.<br class=""></blockquote><br class="">This is what I suspected. I’ll defer to Dave A on how big a concern<br class="">this is, but it seems to me like a bit of a slippery slope towards<br class="">sub-optimal performance.<br class=""></blockquote><br class="">Well, it's unfortunate. We have a similar problem today due to the lack<br class="">of conditional conformance, and we deal with it by injecting an<br class="">underscored requirement where it doesn't belong, then dispatch through<br class="">that. I wonder if the workaround for this limitation is like that, or<br class="">something completely different.<br class=""><br class="">Does this work? If not, why not? If so, what makes it fundamentally<br class="">different, since it is trying to express the same thing through<br class="">different means?<br class=""><br class="">-----<br class=""><br class="">public protocol P {<br class=""> func f()<br class="">}<br class=""><br class="">public protocol Q: P { }<br class=""><br class="">public struct X<T> { let t: T}<br class=""><br class="">internal protocol XFImpl {<br class=""> associatedtype TT: P<br class=""> static func f(X<T>)<br class="">}<br class=""><br class="">extension XFImpl {<br class=""> static func f(X<TT>) { /* general but slow */ }<br class="">}<br class=""><br class="">extension XFImpl where TT: Q {<br class=""> static func f(X<TT>) { /* fast because it takes advantage of T: Q */ }<br class="">}<br class=""><br class="">extension X: P where T: P {<br class=""> struct FImpl : XFImpl {<br class=""> typealias TT = T<br class=""> }<br class=""><br class=""> func f() {<br class=""> FImpl.f(self)<br class=""> }<br class="">}<br class=""><br class="">struct IsQ : Q { }<br class=""><br class="">func generic<U: P>(_ value: u) {<br class=""> value.f()<br class="">}<br class=""><br class="">generic(X<IsQ>())<br class=""><br class="">-- <br class="">-Dave<br class=""><br class="">_______________________________________________<br class="">swift-evolution mailing list<br class=""><a href="mailto:swift-evolution@swift.org" class="">swift-evolution@swift.org</a><br class="">https://lists.swift.org/mailman/listinfo/swift-evolution<br class=""></div></div></blockquote></div><br class=""></body></html>