> On Aug 17, 2016, at 12:35 AM, Slava Pestov <spes...@apple.com> wrote: > > >> On Aug 16, 2016, at 10:16 PM, Charles Srstka <cocoa...@charlessoft.com >> <mailto:cocoa...@charlessoft.com>> wrote: >> >>> On Aug 16, 2016, at 11:42 PM, Slava Pestov <spes...@apple.com >>> <mailto:spes...@apple.com>> wrote: >>>> >>>> Argh, that’s particularly frustrating since in something like ‘func foo<T >>>> : P>(t: T)’ or ‘func foo<S : Sequence>(s: S) where S.IteratorElement: P’, >>>> you’re only ever getting instances anyway since the parameter is in the >>>> input, so calling initializers or static functions isn’t something you can >>>> even do (unless you call .dynamicType, at which point you *do* have a >>>> concrete type at runtime thanks to the dynamic check). >>> >>> Well, if you have ‘func foo<T : P>(t: T)’, then you can write >>> T.someStaticMember() to call static members — it’s true you also have an >>> instance ’t’, but you can also work directly with the type. But I suspect >>> this is not what you meant, because: >> >> Agh, you’re right, I’d forgotten about that. It’s days like this that I miss >> Objective-C’s “It just works” dynamism. ;-) > > Objective-C doesn’t have an equivalent of associated types or contravariant > Self, but I understand your frustration, because Sequence and Equatable are > pervasive in Swift.
I was thinking of Equatable, which in Objective-C was just the -isEqual: method on NSObject, which we usually just started with a dynamic type check in the cases where that mattered. I’m sure performance on Swift’s version is much better, but the ObjC way was refreshingly surprise-free. >> The other trouble is that it’s not just confusing; it can very easily get in >> the way of your work even if you know exactly what’s going on, necessitating >> kludges like AnyHashable just to do things like have a dictionary that can >> take more than one key type (an example that’s particularly irritating since >> the only method you care about, hashValue, is just a plain old Int that >> doesn’t care about the Self requirement at all). I know that a while ago I >> ended up using my own Equatable substitute with an ObjC-style isEqual() >> method on some types, just because actually implementing Equatable was >> throwing a huge spanner into the rest of the design. > > Yeah, AnyHashable is basically a hand-coded existential type. It would also > be possible to do something similar for Equatable, where an AnyEquatable type > could return false for two values with differing concrete types, removing the > need for an == with contra-variant Self parameters. Also: changing something into a class when it otherwise didn’t need to be one, so you can use an ObjectIdentifier as a dictionary key, because using a protocol that conformed to Hashable was dropping an atom bomb on the entire rest of the project. > Generalized existentials eliminate the restriction and thus the hacks. On the > other hand, they add yet more complexity to the language, so designing them > correctly involves difficult tradeoffs. Fair enough. I guess I’ll wait it out a bit and see what the team comes up with. >> Well, the idea was to create an easier-to-implement alternative to >> self-conforming protocols, which could be done if :== were expanded to one >> function that uses ==, and another with the same body that uses :, because I >> was under the impression that the compiler team did not want to implement >> self-conforming protocols. > > I think the underlying machinery would be the same. We only want to compile > the body of a generic function body, without any kind of cloning like in C++ > templates, producing a general uninstantiated runtime form. So :== T > requirements would effectively require self-conforming protocols anyway, > since your function will have to dynamically handle both cases. > > The implementation for self-conforming opaque protocols is not difficult, > because the value itself can already be of any size, so it’s really not a > problem to have an existential in there. In theory, someone could cook it up > in a week or so. > > For class protocols, I don’t know how to do it without an efficiency hit > unfortunately. > > Consider these two functions, taking a homogeneous and heterogeneous array of > a class-bound protocol type: > > protocol P : class {} > > func f<T : P>(array: [T]) {} // this takes an array of pointers to T, because > there’s only one witness table for all of them > func ff(array: [P]) {} // this takes an array of <T, witness table> pairs, > two pointers each, because each element can be a different concrete type > > What you’re saying is that f() should in fact allow both representations, > because you’ll be able to call f() with a value of type [P]. Right now, if we > know a generic parameter is class-constrained, we use a much more efficient > representation for values of that type, that is known to be fixed size in the > LLVM IR. We would have to give that up to allow class-constrained > existentials to self-conform, since now a class-constrained parameter can be > an existential with any number of witness tables. > > There might be some trick for doing this efficiently, but I don’t know of one > yet. > > Of course, we can just say that class-constrained protocols never > self-conform, unless they’re @objc. That seems like a hell of an esoteric > restriction though (can you imagine trying to come up with a clear phrasing > for *that* diagnostic?) > > And if you’re wondering, the reason that @objc protocols self-conform in > Swift today, is because they their existentials don’t have *any* witness > tables — @objc protocol method bodies are found by looking inside the > instance itself. > > AnyObject is the other kind of protocol that self-conforms — you can use it > both as a generic constraint, and as a concrete type bound to a generic > parameter, and it ‘just works’, because again it doesn’t have a witness table. Ah… because of the static dispatch, mapping the protocol members to address offsets which may vary from member to member, as opposed to @objc protocols, which I’d guess are probably doing the old-school lookup by selector name à la objc_msgSend(). Hmm. I’d still probably argue that it’s worth it, because I get the impression that Apple prefers the use of generic sequence and collections for parameters rather than hard-coding arrays, and frankly, with the current behavior it is slightly difficult to do that. I guess it’s up to the compiler team, though. I will say that this has been an interesting discussion. Thanks for offering your knowledge and insight. Charles
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution