> On Aug 17, 2016, at 12:35 AM, Slava Pestov <[email protected]> wrote:
>
>
>> On Aug 16, 2016, at 10:16 PM, Charles Srstka <[email protected]
>> <mailto:[email protected]>> wrote:
>>
>>> On Aug 16, 2016, at 11:42 PM, Slava Pestov <[email protected]
>>> <mailto:[email protected]>> 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
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution