> 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

Reply via email to