On 17.08.2016 13:00, Boris Wang via swift-evolution wrote:
The problem is that:
protocol should not be a type, but it is a type sometime and not type
sometime now.
for exam:
P.Type not same as T.Type
But you can declare a variable of type P.
Protocol should be a contract only, no instances of it.
I'm also confused about this. Tried to follow the whole discussion of
experienced(as I understand) developer Charles and a member of core team,
and saw that even experienced developer had some troubles to understand all
the catches of protocols in Swift.
How we want to make less experienced developers to understand Swift's model
of protocols&conformance&generic&existentials&etc.. While problems begins
with the most used Equatable/Hashable protocols..
Also I believe the syntax of generic constraints in 'where' part is
confusing. I'm about this:
func pick<PepperType:Sequence>(peppers: PepperType) where
PepperType.Iterator.Element == Pepper
If '== Proto' means *exactly* typed as such protocol("let arr : [Proto] =
.."), so IMO ': Proto' should means *typed* as exactly this protocol or any
derived protocol("let arr : [ProtocolDerivedFromProto] = .."). Just in
symmetry with class types in 'where' clause here.
Currently, how to specify 'protocol Proto or its derived protocols" ?
I suggest to discuss changing the rules for 'where' generic filter for
protocols:
1. Don't allow syntax ': P' or '== P' for protocols. Such syntax should be
allowed only for classes, structs & other value types.
2. For protocol introduce 'is P' syntax, which should mean 'typed exactly
as P, or its derived protocol, or as concrete type conformed to P'. I.e.:
func pick<PepperType:Sequence>(peppers: PepperType) where
PepperType.Iterator.Element is Pepper
3. If it is hard to implement (2) today in the "right" way, implement it
with two copies of the same function, one with ":P" and one with "==P"(was
discussed earlier in thread)
And, yes, IMO it is clear that we should be able to specify 'protocol
Proto, its derived protocol, or any concrete type conformed to Proto' just
because we are defining function that work with items that conforms to some
contract, we are not interested how it is *defined* later in code, but
*what it is* actually.
And because we *can* implement this now by copy/paste the *same function*
with *same* name with 1 symbol changed - so, after all, I believe compiler
can simulate this for us.
But yes, we all will wait what core team suggest as improvement in this area.
Charles Srstka via swift-evolution <[email protected]
<mailto:[email protected]>>于2016年8月17日 周三14:11写道:
On Aug 17, 2016, at 12:35 AM, Slava Pestov <[email protected]
<mailto:[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] <mailto:[email protected]>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution