[swift-evolution] namespacing protocols to other types

Kelvin Ma kelvin13ma at gmail.com
Fri Dec 29 21:00:32 CST 2017


Modules in Swift are really designed for code that is intended to be
shipped separately. as Jon found out modules are pretty heavyweight and
introduce a lot of unwanted abstraction and complexity when all you want to
do is write things in a namespace. (Also if you forgot, importing a module
in Swift dumps all of its symbols into the global namespace so Module.Thing
is really meaningless.) sometimes this is a good thing because no one wants
to be writing Glibc.fopen over and over especially when you have to #if
#endif it out every time with Darwin.fopen if you want your library to run
on OSX but this feature also makes modules ineffective as a namespacing
scheme.

On Fri, Dec 29, 2017 at 8:35 PM, Eneko Alonso <eneko.alonso at gmail.com>
wrote:

> …all i wanted to do was write Thing:Namespace.Protocol
>
>
> Have you thought of using Swift modules for that? Maybe that would be a
> better approach than trying to name-space within a module?
>
> Regards,
> Eneko Alonso
>
> On Dec 29, 2017, at 16:51, Kelvin Ma via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> …all i wanted to do was write Thing:Namespace.Protocol
>
> On Thu, Dec 28, 2017 at 4:43 PM, Adrian Zubarev via swift-evolution <
> swift-evolution at 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 at gmail.com)
>> schrieb:
>>
>>
>>
>> On 28. Dec 2017, at 12:34, Adrian Zubarev <adrian.zubarev at devandartist.c
>> om> 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 at 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 at 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 at 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 at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171229/b8887cff/attachment.html>


More information about the swift-evolution mailing list