On Dec 29, 2017, at 16:51, Kelvin Ma via swift-evolution < >> swift-evolution@swift.org> wrote:
On Thu, Dec 28, 2017 at 4:43 PM, Adrian Zubarev via swift-evolution < >> swift-evolution@swift.org> wrote:
Well again I don’t think we should disallow capturing the outer generic
type parameter just because you cannot use the protocol inside the outer
type atm., you still can add a type-eraser. To be honest such usage of the
existential is not even a requirement for the outer type. On there other
hand we might want to set the default for the associated type like I showed
in my previous message. The nested protocol could serve a completely
different purpose. Furthermore I still think that Generic<A>.P and
Generic<B>.P should be distinct protocols just like nested generic and
non-generic types are within an outer generic type. Sure there could be
other problems with ambiguity if you think of something like
GenericViewController<T>.Delegate, but the disambiguation when
conforming to such protocols requires a different solution and is a well
known limitation today.
That said you won’t design such nested types anyways if you know the
existing language limitation. I’d say let’s keep it simple in theory and
just align the nesting behaviour.
About existentials:
For that scenario I can only speak for myself. I wouldn’t want to allow
directly the where clause existentials like this. It is far better and
more readable when we force the where clause on typealiases instead. We
could lift that restriction later if we’d like to, but not the other way
around. I think it’s okay if we start with a small restriction first and
see if it adopts well (this is MHO), because this way it shouldn’t harm
anybody.
Am 28. Dezember 2017 um 21:51:29, Karl Wagner (razielim@gmail.com)
schrieb:
On 28. Dec 2017, at 12:34, Adrian Zubarev <adrian.zubarev@devandartist.c >>> > wrote:
I disagree with some of your points. Do begin with, I don’t think we
should disallow capturing the generic type parameter, because I think there
might be a good way to prevent parameterization of nested protocols.
To me this only feels like a natural consequence of nesting protocols
anyways. To achieve this we have to provide an explicit associated type
which will have a default type that refers to the captured outer generic
type parameter. At this point we discover another issue that we cannot
disambiguate generic type parameter like associated types yet and would be
forced to name the associated type of the protocol differently.
struct Generic<T> {
protocol P {
associatedtype R = T
func f() -> R
}
}
As you can see I didn’t include the variable in this example, because
existential are orthogonal this issue. David Hart and I still want to write
a proposal to allow the where clause on typealiases - maybe after the
forum officially launches.
Above I said that there is an issue and provided an example that would
solve that issue with todays syntax, but I’d rather expand this idea.
Consider this syntax of a generic type and a protocol with an associated
type.
protocol Proto {
associatedtype Element
}
Proto.Element // This is an error like this, but it's still allowed in a generic context
func function<P : Proto>(_: P) where P.Element == Int {}
protocol OtherProto : Proto where Element == Int {}
struct Test<Element> {}
extension Test where Element == Int {}
Test.Element // Can/should we allow this?
If we could allow this in Swift then the above example with the nested
protocol could be disambiguated nicely.
struct Generic<T> {
protocol P {
associatedtype T = Generic.T
func f() -> T
}
}
Remember that Generic.T is only the default for P.T if you don’t set it
yourself but when you conform or use that that protocol (in a generic
context) you can still set it differntly.
This consequence disallows protocol parameterization through nesting in
a generic types, but still behaves very similar to nested generic types:
struct Test<T> {
struct NonGeneric {
var t: T
}
struct Generic<R> {
var t: T
var r: R
}
}
_ = Test<String>.NonGeneric(t: "
")
_ = Test<String>.Generic<Int>(t: "
", r: 42)
——
Yeah, that’s all well and good. I don’t think we should parameterise
protocols either; it feels like Swift hasn’t been designed with those in
mind, and that’s fine. You would get in to all kinds of horrible conflicts
if you tried to conform to both Generic<Int>.P and Generic<String>.P,
because most functions would have the same signature but possibly very
different implementations. You would likely end up having to separate the
conformances by using a wrapper struct — in which case, why not just make
them the same protocol and have the existing duplicate-conformance rules
take care of it?
An earlier version of the proposal included something like you describe.
Basically, Generic<Int>.P and Generic<String>.P would be the same protocol.
They would have an associated type to represent the parameter from
Generic<T>, and within Generic<T>, all references to P would be implicitly
constrained so that P.T == Self.T. You would write conformances to
“Generic.P” with a constraint for T, as you do today.
And for the existential variable inside Genric it really should be
something like this (when the where clause is allowed and if we can refer
differently to generic type parameters as well):
struct Generic<T> {
…
typealias PConstrainedByT = P where T == Self.T
var object: PConstrainedByT
}
If we have that ability, then we can totally do capturing. Forgive me,
but I understand that as pretty-much the same as generalised existentials
(without local type binding).
If I can write the type of object as an existential of (generic protocol
+ constraints) via a typealias, then surely I must also be able to do it
directly? So I could also write:
struct Generic<T> {
var object: P where T == Self.T
}
Anyway, I thought that was not on the table, and in any case I’m
convinced that it should be a separate proposal. This gets to the heart of
the interaction between generic types and protocols, and we all know it’s
not always a smooth transition (hello AnyCollection, AnyHashable, etc...).
We can cover the common cases (i.e. the Apple frameworks) without requiring
capturing - especially since it’s apparently not too difficult to implement
- and build from there.
- Karl
Am 27. Dezember 2017 um 19:53:36, Karl Wagner via swift-evolution (
swift-evolution@swift.org) schrieb:
Yeah I wrote that proposal. I eventually stripped it down to just
disallow all capturing, but it was still not selected by the core team for
review ¯\_(ツ)_/¯
As for capturing semantics, once you start working through use-cases,
it’s becomes clear that it's going to require generalised existentials.
Otherwise, how would you use a Generic<T>.P?
struct Generic<T> {
protocol P { func f() -> T }
var object: P // uh-oh! ‘Generic protocol can only be used as a
generic parameter constraint'
}
So, you would need to add a generic parameter to actually *use* P from
within Generic<T>, which of course limits you to a single concrete type of
P:
struct Generic<T, TypeOfP> where TypeOfP: Self.P { // Could this
even work? What if P captures TypeOfP?
protocol P { /* … */ }
var object: TypeOfP
}
Which is just yucky.
Ideally, the type of ‘object’ should be ‘Any<P where P.T == T>’, to
express that it can be any conforming type with the appropriate
constraints. You wouldn’t need to write that all out; we could infer that
capturing is equivalent to a same-type constraint (or perhaps one of these
“generalised supertype constraints” that were pitched recently). But we
can’t express those kinds of existentials everywhere in the type-system
today, so most examples of capturing fall down pretty quickly.
- Karl
On 25. Dec 2017, at 03:56, Slava Pestov via swift-evolution < >>> swift-evolution@swift.org> wrote:
There was a proposal to allow protocols to be nested inside types at one
point but it didn’t move forward.
Basically, if the outer type is a non-generic class, struct or enum,
there’s no conceptual difficulty at all.
If the outer type is a generic type or another protocol, you have a
problem where the inner protocol can reference generic parameters or
associated types of the outer type. This would either have to be banned, or
we would need to come up with coherent semantics for it:
struct Generic<T> {
protocol P {
func f() -> T
}
}
struct Conforms : Generic<Int>.P {
func f() -> Int { … } // Like this?
}
let c = Conforms()
c is Generic<String>.P // is this false? Ie, are Generic<Int>.P and
Generic<String>.P different protocols?
Slava
On Dec 24, 2017, at 6:53 PM, Kelvin Ma via swift-evolution < >>> swift-evolution@swift.org> wrote:
is there a reason why it’s not allowed to nest a protocol declaration
inside another type?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution