[swift-evolution] [Draft] open and public protocols

Xiaodi Wu xiaodi.wu at gmail.com
Sun Feb 19 17:03:22 CST 2017


On Sun, Feb 19, 2017 at 4:51 PM, Adrian Zubarev <
adrian.zubarev at devandartist.com> wrote:

> Matthew has pretty much summed up everything with a single question in his
> last reply. It makes me tired of repeating myself over and over again.
>
> Here’s the challenge for you:
>
> Implement a subscript where the first parameter is a String (trivial), but
> the second to n are String’s and/or Int’s. There is no restriction of order
> for the Int’s and String’s and there could be as many as you’d like, so no
> overloads are allowed (a hint: variadics). You should be able to use
> variables in that subscript and literals. You’re not allowed to use enum
> constructors
>
Why not? You want a parameter that takes either String or Int. In another
language, you might express this as `String | Int`, but Chris Lattner and
others have said on this list that enums are the intended way of expressing
this in Swift.

> and the client should not be able to extend the set of types you can pass
> to that subscript. That said it can look like this: someInstance["key",
> "key1", 1, stringKeyInstance, intIndexInstance, intIndexInstance, …]
>
> Creating hacks like unreachable types with hidden inits is prohibited.
>
> Have fun!
>
>
>
> --
> Adrian Zubarev
> Sent with Airmail
>
> Am 19. Februar 2017 um 23:25:11, Xiaodi Wu (xiaodi.wu at gmail.com) schrieb:
>
> Sorry, I have read through this thread twice and do not understand the
> point you are making. Can you explain your example once more?
>
> Specifically, I do not understand why it is that your code should have
> problems with third-party types that conform to your protocol. If your
> protocol has requirements that third-party types cannot fulfill, then
> naturally those requirements will prevent a third-party from conforming new
> types to your protocol. OTOH, if your protocol does not have any
> requirements that third-party types cannot fulfill, then your code that
> accepts anything that conforms to your protocol should be indifferent to
> whether the conforming type is defined by you or someone else. After all, a
> conforming type would fulfill all the semantic and syntactic requirements
> of the protocol! If your protocol has requirements that you are unable to
> state in code, causing third-party types to conform that really shouldn't,
> then that is a case for additional features that allow you to express those
> requirements more accurately, not an argument for having protocols that
> can't be conformed to by a third-party. What am I missing?
>
>
> On Sun, Feb 19, 2017 at 11:53 AM, Adrian Zubarev via swift-evolution <
> swift-evolution at swift.org> wrote:
>
>> That’s the whole point I was making. :) Thank you Matthew. That makes my
>> example a real world example and not just some bike shedding.
>>
>> --
>> Adrian Zubarev
>> Sent with Airmail
>>
>> Am 19. Februar 2017 um 18:50:31, Matthew Johnson (matthew at anandabits.com)
>> schrieb:
>>
>>
>>
>> Sent from my iPad
>>
>> On Feb 19, 2017, at 11:29 AM, David Waite via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>> Just FYI, I solved this issue in my own library (which included a json
>> jpointer implementation) via:
>>
>> public enum SubscriptParameter {
>>   case string(String)
>>   case int(Int)
>> }
>>
>> extension SubscriptParameter : ExpressibleByIntegerLiteral {
>>   public init(integerLiteral value: Int) {
>>     self = .int(value)
>>   }
>> }
>>
>> extension SubscriptParameter : ExpressibleByStringLiteral {
>>   public init(stringLiteral value: String) {
>>     self = .string(value)
>>   }
>>   public init(extendedGraphemeClusterLiteral value: String) {
>>     self.init(stringLiteral: value)
>>   }
>>
>>   public init(unicodeScalarLiteral value: String) {
>>     self.init(stringLiteral: value)
>>   }
>> }
>>
>> extension SubscriptParameter : CustomStringConvertible {
>>   public var description: String {
>>     switch self {
>>     case .string(let str):
>>       return "\"\(str)\""
>>     case .int(let i):
>>       return String(i)
>>     }
>>   }
>> }
>>
>> func debug(_ path:SubscriptParameter...) {
>>   print("path is \(path)")
>> }
>>
>> debug(1, "foo", 2, "bar”) // path is [1, “foo”, 2, “bar”]
>>
>>
>> Can you make this work with variables - not just literals - and still
>> prevent users from extending the set of types and without requiring an enum
>> case constructor to be used by clients?
>>
>>
>>
>> On Feb 19, 2017, at 1:14 AM, Adrian Zubarev <
>> adrian.zubarev at devandartist.com> wrote:
>>
>> If you haven’t followed the other thread Matthew previously opened than
>> you have missed the example I showed there.
>>
>> Here it is again:
>>
>> public protocol SubscriptParameterType {
>>
>>     // This property was needed to prevent the client from breaking
>>     // the library by conforming to the protocol, but I'd like to
>>     // keep it invisible for the client, or even better prevent the
>>     // client from conforming to the protocol.
>>     var parameter: Document.SubscriptParameter { get }
>> }
>>
>> extension Document {
>>
>>     public enum SubscriptParameter {
>>
>>         case string(String)
>>         case integer(Int)
>>     }
>> }
>>
>> extension String : SubscriptParameterType {
>>
>>     public var parameter: Document.SubscriptParameter {
>>
>>         return .string(self)
>>     }
>> }
>>
>> extension Int : SubscriptParameterType {
>>
>>     public var parameter: Document.SubscriptParameter {
>>
>>         return .integer(self)
>>     }
>> }
>>
>> // Somewhere inside the `Document` type
>> public subscript(firstKey: String, parameters: SubscriptParameterType...) -> Value? { … }
>>
>> The absence of closed protocols forced me to create a special requirement
>> on that protocol to prevent the client from conforming to that protocol and
>> passing instances of other types my API wouldn’t want to deal with. That
>> creates unnecessary copies and I need to unpack the enum payload to find
>> out which type the user passed. Instead I could simply close the protocol,
>> wouldn’t need the requirement to exist and I could simply cast the type to
>>  String or Int when needed.
>>
>> That implementation enables more safe queries of my Document type like
>>
>> document["key1", intIndexInstance, stringKeyInstance, 10, "key"]
>>
>> rather than
>>
>> document["key1/\(intIndexInstance)/\(stringKeyInstance)/10/key"].
>> ------------------------------
>>
>> Here is a list of hidden and semi-hidden protocols from the standard
>> library that could be closed. Formatted version: https://gist.github.c
>> om/DevAndArtist/168c800d784829be536c407311953ab7
>> Path Protocol
>> /swift/stdlib/public/core/AnyHashable.swift:16
>> _HasCustomAnyHashableRepresentation
>> /swift/stdlib/public/core/BidirectionalCollection.swift:21
>> _BidirectionalIndexable
>> /swift/stdlib/public/core/BridgeObjectiveC.swift:19 _ObjectiveCBridgeable
>> /swift/stdlib/public/core/Collection.swift:20 _IndexableBase
>> /swift/stdlib/public/core/Collection.swift:176 _Indexable
>> /swift/stdlib/public/core/CompilerProtocols.swift:193
>> _ExpressibleByBuiltinIntegerLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:240
>> _ExpressibleByBuiltinFloatLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:283
>> _ExpressibleByBuiltinBooleanLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:316
>> _ExpressibleByBuiltinUnicodeScalarLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:350
>> _ExpressibleByBuiltinExtendedGraphemeClusterLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:398
>> _ExpressibleByBuiltinStringLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:407
>> _ExpressibleByBuiltinUTF16StringLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:670
>> _ExpressibleByStringInterpolation
>> /swift/stdlib/public/core/CompilerProtocols.swift:709
>> _ExpressibleByColorLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:720
>> _ExpressibleByImageLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:730
>> _ExpressibleByFileReferenceLiteral
>> /swift/stdlib/public/core/CompilerProtocols.swift:750
>> _DestructorSafeContainer
>> /swift/stdlib/public/core/FixedPoint.swift.gyb:53 _Integer
>> /swift/stdlib/public/core/FixedPoint.swift.gyb:70 _SignedInteger
>> /swift/stdlib/public/core/FixedPoint.swift.gyb:108
>> _DisallowMixedSignArithmetic
>> /swift/stdlib/public/core/Hashable.swift:16 _Hashable
>> /swift/stdlib/public/core/Index.swift:16 _Incrementable
>> /swift/stdlib/public/core/IntegerArithmetic.swift.gyb:33
>> _IntegerArithmetic
>> /swift/stdlib/public/core/Mirror.swift:721 _DefaultCustomPlaygroundQuickL
>> ookable
>> /swift/stdlib/public/core/MutableCollection.swift:20 _MutableIndexable
>> /swift/stdlib/public/core/NewtypeWrapper.swift.gyb:16
>> _SwiftNewtypeWrapper
>> /swift/stdlib/public/core/Pointer.swift:16 _Pointer
>> /swift/stdlib/public/core/RandomAccessCollection.swift:20
>> _RandomAccessIndexable
>> /swift/stdlib/public/core/RangeReplaceableCollection.swift.gyb:27
>> _RangeReplaceableIndexable
>> /swift/stdlib/public/core/ReflectionLegacy.swift:41 _Mirror
>> /swift/stdlib/public/core/ShadowProtocols.swift:27 _ShadowProtocol
>> /swift/stdlib/public/core/ShadowProtocols.swift:31 _NSFastEnumeration
>> /swift/stdlib/public/core/ShadowProtocols.swift:41 _NSEnumerator
>> /swift/stdlib/public/core/ShadowProtocols.swift:51 _NSCopying
>> /swift/stdlib/public/core/ShadowProtocols.swift:61 _NSArrayCore
>> /swift/stdlib/public/core/ShadowProtocols.swift:83 _NSDictionaryCore
>> /swift/stdlib/public/core/ShadowProtocols.swift:125 _NSDictionary
>> /swift/stdlib/public/core/ShadowProtocols.swift:137 _NSSetCore
>> /swift/stdlib/public/core/ShadowProtocols.swift:171 _NSSet
>> /swift/stdlib/public/core/ShadowProtocols.swift:177 _NSNumber
>> /swift/stdlib/public/core/ShadowProtocols.swift:187 _NSArrayCore
>> /swift/stdlib/public/core/ShadowProtocols.swift:188 _NSDictionaryCore
>> /swift/stdlib/public/core/ShadowProtocols.swift:189 _NSSetCore
>> /swift/stdlib/public/core/StringBridge.swift:194 _NSStringCore
>> /swift/stdlib/public/SDK/Foundation/NSError.swift:353
>> _ObjectiveCBridgeableError
>> /swift/stdlib/public/SDK/Foundation/NSError.swift:379 __BridgedNSError
>> /swift/stdlib/public/SDK/Foundation/NSError.swift:446 _BridgedNSError
>> /swift/stdlib/public/SDK/Foundation/NSError.swift:456
>> _BridgedStoredNSError
>> /swift/stdlib/public/SDK/Foundation/NSError.swift:564 _ErrorCodeProtocol
>>
>>
>>
>> --
>> Adrian Zubarev
>> Sent with Airmail
>>
>> Am 19. Februar 2017 um 07:59:45, David Waite via swift-evolution (
>> swift-evolution at swift.org) schrieb:
>>
>> I am unsure if this feature is a good idea. Does someone have a
>> real-world use for this which isn’t just hiding strong implementation
>> coupling behind a protocol?
>>
>> When I consume a protocol, it is under the assumption that the protocol
>> is documented such that I would be able to work against *any*
>> implementation of the protocol. With a closed protocol, I would have to
>> assume that there are significant side effects, either undocumented or
>> difficult for a third party to duplicate. To my experience, that sounds
>> brittle.
>>
>> Assuming you aren’t switching on the implementing type of a protocol
>> (which itself can be a sign that your design isn’t properly using
>> polymorphism), one could get this design by creating a struct with the
>> interface desired, and passing invocations through to an internal protocol
>> reference.
>>
>> -DW
>>
>> > On Feb 18, 2017, at 1:41 PM, Matthew Johnson via swift-evolution <
>> swift-evolution at swift.org> wrote:
>> >
>> > Now that we’re in phase 2 I’d like to officially propose we introduce
>> `open` protocols and require conformances to `public` protocols be inside
>> the declaring module. Let’s use this thread for feedback on the official
>> proposal. After a healthy round of discussion I’ll open a PR to submit it
>> for review.
>> >
>> >
>> > # Feature name
>> >
>> > * Proposal: [SE-NNNN](NNNN-open-public-protocols.md)
>> > * Authors: [Matthew Johnson](https://github.com/anandabits)
>> > * Review Manager: TBD
>> > * Status: **Awaiting review**
>> >
>> > ## Introduction
>> >
>> > This proposal introduces `open protocol` and changes the meaning of
>> `public protocol` to match the meaning of `public class` (in this case,
>> conformances are only allowed inside the declaring module).
>> >
>> > The pitch thread leading up to this proposal was: [consistent public
>> access modifiers](https://lists.swift.org/pipermail/swift-evolution
>> /Week-of-Mon-20170206/031653.html)
>> >
>> > ## Motivation
>> >
>> > A general principle the Swift community has adopted for access control
>> is that defaults should reserve maximum flexibility for a library. The
>> ensures that any capabilities beyond mere visibility are not available
>> unless the author of the library has explicitly declared their intent that
>> the capabilities be made available. Finally, when it is possible to switch
>> from one semantic to another without breaking clients (but not vice-versa)
>> we should prefer the more forgiving (i.e. fixable) semantic as the (soft)
>> default.
>> >
>> > `public` is considered a "soft default" in the sense that it is the
>> first access modifier a user will reach for when exposing a declaration
>> outside of the module. In the case of protocols the current meaning of
>> `public` does not meet the principle of preserving maximum flexibility for
>> the author of the library. It allows users of the library to conform to the
>> protocol.
>> >
>> > There are good reasons a library may not wish to allow users to add
>> conformances to a protocol. For example, it may not wish to expose the
>> conforming concrete types. While similar behavior could be accomplished
>> with an enum if cases could be private, that requires an implementation to
>> use switch statements rather than polymorphism.
>> >
>> > Even if all the conforming types are also public there are cases where
>> polymorphism is the preferred implementation. For example, if the set of
>> conforming types is not expected to be fixed (despite all being inside the
>> library) the authors may not want to have to maintain switch statements
>> every time they need to add or remove a confroming type which would be
>> necessary if an enum were used instead. Polymorphism allows us to avoid
>> this, giving us the ability to add and remove conforming types within the
>> implementation of the library without the burden of maintaining switch
>> statements.
>> >
>> > Aligning the access modifiers for protocols and classes allows us to
>> specify both conformable and non-conformable protocols, provides a soft
>> default that is consistent with the principle of (soft) defaults reserving
>> maximum flexibility for the library, and increases the overall consistency
>> of the language by aligning the semantics of access control for protocols
>> and classes.
>> >
>> > The standard library currently has at least one protocol (`MirrorPath`)
>> that is documented as disallowing client conformances. If this proposal is
>> adopted it is likely that `MirrorPath` would be declared `public protocol`
>> and not `open protocol`.
>> >
>> > Jordan Rose has indicated that the Apple frameworks also include a
>> number of protocols documented with the intent that users do not add
>> conformances. Perhaps an importer annotation would allow the compiler to
>> enforce these semantics in Swift code as well.
>> >
>> > ## Proposed solution
>> >
>> > The proposed solution is to change the meaning of `public protocol` to
>> disallow conformances outside the declaring module and introduce `open
>> protocol` to allow conformances outside the decalring module (equivalent to
>> the current meaning of `public protocol`).
>> >
>> > ## Detailed design
>> >
>> > The detailed design is relatively straightforward but there are three
>> important wrinkles to consider.
>> >
>> > ### User refinement of public protocols
>> >
>> > Consider the following example:
>> >
>> > ```swift
>> > // Library module:
>> > public protocol P {}
>> > public class C: P {}
>> >
>> > // User module:
>> > protocol User: P {}
>> > extension C: User {}
>> > ```
>> >
>> > The user module is allowed to add a refinement to `P` because this does
>> not have any impact on the impelementation of the library or its possible
>> evolution. It simply allows the user to write code that is generic over a
>> subset of the conforming types provided by the library.
>> >
>> > ### Public protocols with open conforming classes
>> >
>> > Consider the following example:
>> >
>> > ```swift
>> > public protocol P P{}
>> > open class C: P {}
>> > ```
>> >
>> > Users of this module will be able to add subclasses of `C` that have a
>> conformance to `P`. This is allowed becuase the client of the module did
>> not need to explicitly declare a conformance and the module has explicitly
>> stated its intent to allow subclasses of `C` with the `open` access
>> modifier.
>> >
>> > ### Open protocols that refine public protocols
>> >
>> > Consider the following example:
>> >
>> > ```swift
>> > // library module:
>> > public protocol P {}
>> > open protocol Q: P {}
>> > open protocol R: P {}
>> >
>> > // user module:
>> > struct S: P {} // error `P` is not `open`
>> > struct T: Q {} // ok
>> > struct U: R {} // ok
>> > ```
>> >
>> > The user module is allowed to introudce a conformance to `P`, but only
>> indirectly by also conforming to `Q`. The meaning we have ascribed to the
>> keywords implies that this should be allowed and it offers libraries a very
>> wide design space from which to choose. The library is able to have types
>> that conform directly to `P`, while placing additional requirements on user
>> types if necessary.
>> >
>> > ## Source compatibility
>> >
>> > This proposal breaks source compatibility, but in a way that allows for
>> a simple mechanical migration. A multi-release stratgegy will be used to
>> roll out this proposal to provide maximum possible source compatibility
>> from one release to the next.
>> >
>> > 1. In Swift 4, introduce the `open` keyword and the `@nonopen`
>> attribute (which can be applied to `public protocol` to give it the new
>> semantics of `public`).
>> > 2. In Swift 4 (or 4.1 if necessary) start warning for `public protocol`
>> with no annotation.
>> > 3. In the subsequent release `public protocol` without annotation
>> becomes an error.
>> > 4. In the subsequent relase `public protocol` without annotation takes
>> on the new semantics.
>> > 5. `@nonopen` becomes a warning, and evenutally an erro as soon as we
>> are comfortable making those changes.
>> >
>> > ## Effect on ABI stability
>> >
>> > I would appreciate it if others can offer input regarding this section.
>> I believe this proposal has ABI consequences, but it's possible that it
>> could be an additivie ABI change where the ABI for conformable protocols
>> remains the same and we add ABI for non-conformable protocols later. If
>> that is possible, the primary impact would be the ABI of any standard
>> library protocols that would prefer to be non-conformable.
>> >
>> > ## Effect on API resilience
>> >
>> > This proposal would may impact one or more protocols in the standard
>> library, such as `MirrorPath`, which would likely choose to remain `public`
>> rather than adopt `open`.
>> >
>> > ## Alternatives considered
>> >
>> > The primary alternatives are to either make no change, or to add
>> something like `closed protocol`. The issues motivating the current
>> proposal as a better alternative than either of these options are covered
>> in the motivation section.
>> >
>> > _______________________________________________
>> > 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/20170219/9d44e425/attachment.html>


More information about the swift-evolution mailing list