[swift-evolution] [Proposal draft] Conditional conformances

Callionica (Swift) swift-callionica at callionica.com
Sun Oct 2 10:56:19 CDT 2016


Interesting comment about worries that you'd be dispatched to the least
good SS version . A different language with different constraints, but when
LINQ to Objects implementers needed to provide optimized c# implementations
for some operations they chose a runtime type check to dispatch to the
optimized version. For example, while API is exposed as IEnumerable<T>
there are method implementations that check for ICollection<T> at runtime
in order to hit more efficient implementations. So static dispatch is good,
but win for collections often big enough to overcome a hit from dynamic
dispatch.

On Sunday, October 2, 2016, plx via swift-evolution <
swift-evolution at swift.org> wrote:

>
> On Sep 30, 2016, at 1:23 PM, Douglas Gregor <dgregor at apple.com
> <javascript:_e(%7B%7D,'cvml','dgregor at apple.com');>> wrote:
>
>
> This is purely anecdotal but I had a lot of utility code laying around
> that I’d marked with notes like `// TODO: revisit once conditional
> conformances are available`.
>
> When I was leaving those notes I was expecting to need overlapping
> conformances often, but I reviewed them *before* replying and I actually
> haven’t found an example where having overlapping conformances is both (1)
> a significant win and also (2) a win in a way that’d be of broad, general
> interest.
>
> - 80% have no real need for overlapping conditional conformances
> - 15% might have “elegance gains” but nothing practically-significant
> - 5% would *probably* see real gains but are likely not of broad interest
>
> …which wasn’t what I was expecting, but leaves me a lot more comfortable
> without overlapping conformances for now than I was in the abstract.
>
>
> Very interesting, thanks for doing this review!
>
>
> I've taken the time to provide a bit more color on the 80/15/5 breakdown
> because I don't see much discussion for this proposal in terms of concrete
> situations...just theoretical concerns and theoretical possibilities. I
> don't have any completed code either, but I have notes and to-do lists for
> things I was planning to do, and I think seeing even some semi-concrete
> scenarios might be helpful here.
>
> The "80%" are generally analogous to the `SomeWrapper` in the writeup; as
> a concrete example, I was waiting on the availability of conditional
> conformances to resume work on an emulation of structural unions, e.g.
> something like:
>
>   enum Sum2<A,B> {
>     case a(A)
>     case b(B)
>   }
>
> ...(and analogously for 3, 4, as-necessary).
>
> There's a very obvious way to write `extension Sum2 : Equatable where
> A:Equatable, B:Equatable {}`...and at the time I set this aside, I was
> expecting to also want to come back and have additional conformances for
> things like `...where A:Equatable, B:AnyObject` (using `===` for comparing
> `B`) and so on for other combinations.
>
> Upon revisiting such things in light of the proposal, I now think
> differently: for this case it seems like a better long-term approach
> anyways to stick to a single conformance and work with it like this:
>
>   extension Sum2:Equatable where A:Equatable, B:Equatable {
>     // details elided
>   }
>
>   /// Adaptor using `ObjectIdentifier` to implement `==`.
>   struct ObjectWrapper<Wrapped:AnyObject> : Equatable, Hashable {
>     let wrapped: Wrapped
>   }
>
> ...as upon reflection I really would prefer dealing with the hassle of
> working with `Sum2<A,ObjectWrapper<B>>` in situations where -- in theory --
> `Sum2<A,B>` could do -- to the hassle of writing out 4+ conformances for
> `Sum2` (and so on...even with nice code-gen tools that's going to be a lot
> of bloat!).
>
> What changed my mind was tracing through the implications of conditional
> conformances for the use-site ergonomics of adaptors like `ObjectWrapper`
> above; what I mean is, suppose I have a protocol like this:
>
>   protocol WidgetFactory {
>     associatedtype Widget
>     associatedtype Material
>
>     func produceWidget(using material: Material) -> Widget
>   }
>
> ...then it's rather easy to simply write this type of boilerplate:
>
>   extension ObjectWrapper: WidgetFactory where Wrapped: WidgetFactory {
>     typealias Widget = Wrapper.Widget
>     typealias Material = Wrapper.Material
>
>     func produceWidget(using material: Material) -> Widget {
>       return base.produceWidget(using: material)
>     }
>   }
>
> ...which thus means I have the tools I need to make my use of wrappers
> like the `ObjectWrapper` largely transparent at the use sites I care about;
> e.g. I can write a single conditional conformance like this:
>
>   extension Sum2: WidgetFactory
>     where
>     A:WidgetFactory, B:WidgetFactory,
>     A.Material == B.Material,
>     A.Widget == B.Widget {
>
>     typealias Widget = A.Widget
>     typealias Material = A.Material
>
>     func produceWidget(using material: Material) throws -> Widget {
>       switch self {
>         case let .a(aa): return aa.produceWidget(using: material)
>         case let .b(bb): return bb.produceWidget(using: material)
>       }
>     }
>
>   }
>
> ...and it will apply even in situations where circumstances left me using
> `ObjectWrapper` (or similar) on any of the type parameters to `Sum2` (e.g.
> if I also needed an `Equatable` conformance for whatever reason).
>
> At least for now--when I'm still just revisiting plans and thinking about
> it in light of the proposal--I really would prefer having a simpler
> language and writing this type of boilerplate, than having a more-complex
> language and writing the *other* type of boilerplate (e.g. the 4+
> `Equatable` conformances here, and so on for other situations).
>
> Note that I'm not claiming the above is the only use for overlapping
> conditional conformances -- not at all! -- just that situations like the
> above comprise about 80% of the things I was intending to do with
> conditional conformances...and that after revisiting them expecting to be
> troubled by the proposed banning of overlapping conformances, I'm now
> thinking I'd wind up not using the overlapping-conformance approach in such
> cases even if it were available.
>
> So that's the first 80%.
>
> Moving on, the next 15% are places where there's some forced theoretical
> or aesthetic inelegance due to the lack of overlapping conformances, but
> none of these seem to have any significant practical import.
>
> A representative case here is that I currently have the following pair:
>
>   /// `ChainSequence2(a,b)` enumerates the elements of `a` then `b`.
>   struct ChainSequence2<A:Sequence,B:Sequence> : Sequence
>     where A.Iterator.Element == B.Iterator.Element  {
>     // elided
>   }
>
>   /// `ChainCollection2(a,b)` enumerates the elements of `a` then `b`.
>   struct ChainCollection2<A:Collection,B:Collection> : Collection
>     where A.Iterator.Element == B.Iterator.Element {
>     // ^ `where` is not quite right, see below
>   }
>
> ...and obviously conditional conformances will allow these to be
> consolidated into a single `Chain2` type that then has appropriate
> conditional conformances (and for which the cost/benefit for me will tip in
> favor of adding conditional conformances to `BidirectionalCollection` and
> `RandomAccessCollection`, also).
>
> On paper--e.g., theoretically--the lack of overlapping conformances leaves
> in a few aesthetic issues...for example, at present `ChainCollection2`
> actually has to be one of these:
>
>   // "narrower" option: not all `A`, `B` can necessarily be used together:
>   struct ChainCollection2<A:Collection,B:Collection> : Collection
>     where
>     A.Iterator.Element == B.Iterator.Element,
>     A.IndexDistance == B.IndexDistance {
>     typealias IndexDistance = A.IndexDistance
>   }
>
>   // "wasteful" option: theoretically in some cases we are "overpaying"
> and
>   // using a stronger `IndexDistance`, but now we can use any `A` and `B`
>   struct ChainCollection2<A:Collection,B:Collection> : Collection
>     where A.Iterator.Element == B.Iterator.Element {
>     typealias IndexDistance = IntMax
>   }
>
> With overlapping conditional conformances you could have both: one
> conformance that uses base collections' `IndexDistance` when possible, and
> another that uses `IntMax` when necessary...but without conditional
> conformances it's necessary to choose between the "narrower" approach or
> the "wasteful" approach (preserving the status quo).
>
> If you're following along I'm sure you're aware that in this specific
> case, this "choice" is purely academic (or purely aesthetic)...if you go
> with the `IntMax` route there's almost always going to be between "no
> actual difference" and "no measurable difference", so even if it *maybe*
> feels a bit icky the right thing to do is get over it and stop making a
> mountain out of an anthill.
>
> Note that I'm well aware that you can choose to see this as a concrete
> instance of a more-general problem -- that the lack of overlapping
> conformances would potentially leave a lot of performance on the table due
> to forcing similar decisions (and in contexts where there *would* be a real
> difference!) -- but speaking personally I couldn't find very much in my
> "chores pending availability of conditional conformance" that both (a) fell
> into this category and (b) had more than "aesthetic" implications.
>
> This brings me to that last 5% -- the handful of things for which
> overlapping conformances have nontrivial benefits -- and my conclusion here
> is that these tended to be things I doubt are of general interest.
>
> An example here is that I like to use a function that takes two sequences
> and enumerates their "cartesian product", with the following adjustments:
>
> - no specific enumeration *ordering* is guaranteed
> - does something useful even with infinite, one-shot sequences...
> - ...meaning specifically that it will eventual-visit any specific pair
> (even when one or both inputs are infinite, one-shot)
>
> ...(useful for doing unit tests, mostly), which to be done "optimally"
> while also dotting all the is and crossing all the ts would currently
> require at least 8 concrete types:
>
> - 4 sequences, like e.g.:
>   - UnorderedProductSS2<A:Sequence, B:Sequence>
>   - UnorderedProductSC2<A:Sequence, B:Collection>
>   - UnorderedProductCS2<A:Collection, B:Sequence>
>   - UnorderedProductCC2<A:Collection, B:Collection>
> - 4 iterators (one for each of the above)
>
> ...since you need to use a different iteration strategy for each (yes you
> don’t *need* 8 types, but I’m trying to “dott all is, cross all ts” here).
>
> In theory overlapping conditional conformances could be used to cut that
> down to only 5 types:
>
> - 1 type like `UnorderedProduct<A:Sequence,B:Sequence>`
> - the same 4 iterators from before, each used with the appropriate
> conformance
>
> ...which *is* less code *and* seemingly provides nontrivial gains (the
> `SS` variant must maintain buffers of the items it's already seen from each
> underlying sequence, but the others have no such requirement).
>
> But, to be honest, even if those gains are realized, this is the kind of
> situation I'm perfectly comfortable saying is a "niche" and neither broadly
> relevant to the majority of Swift developers nor broadly relevant to the
> majority of Swift code; if overlapping conformances were available I'd use
> them here, but I'm not going to ask for them just to be able to use them
> here.
>
> Also, I'm skeptical these gains would be realized in practice: between the
> proposed "least specialized conformance wins" rule and Swift's existing
> dispatch rules in generic contexts, it seems like even if overlapping
> conformances *were* allowed and I *did* use them, I'd still wind up getting
> dispatched to the pessimal `SS` variant in many cases for which I'd have
> been hoping for one of the more-optimal versions.
>
> So between the niche-ness of such uses -- and their being 5% or less of
> what I was hoping to do -- and my skepticism about how dispatch would pan
> out in practice, I can't get behind fighting for overlapping conformances
> at this time unless they'd be permanently banned by banning them now.
>
> As already stated, I do think that their absence *will* reveal some *true*
> pain points, but I think it makes sense to adopt a "wait-and-see" approach
> here as some more-targeted solution could wind up being enough to address
> the majority of those future pain points.
>
> These are my more-detailed thoughts after looking at what I was planning
> to do with conditional conformances once the became available. I realize it
> doesn't touch on every conceivable scenario and every conceivable use, but
> I want to reiterate that I did my review expecting to find a bunch of
> things that I could use as justifications for why Swift absolutely should
> have overlapping conditional conformances right now...but on actually
> looking at my plans, I couldn't find anything for which I actually felt
> that way.
>
>
> - Doug
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20161002/e0b1d29b/attachment.html>


More information about the swift-evolution mailing list