On Aug 17, 2016, at 12:35 AM, Slava Pestov <spestov@apple.com> wrote:
On Aug 16, 2016, at 10:16 PM, Charles Srstka <cocoadev@charlessoft.com> > wrote:
On Aug 16, 2016, at 11:42 PM, Slava Pestov <spestov@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