[swift-evolution] [Proposal] Foundation Swift Archival & Serialization

Itai Ferber iferber at apple.com
Wed Mar 15 20:46:55 CDT 2017


Thanks Joe, and thanks for passing this along!

To those who are curious, we use abstract base classes for a cascading 
list of reasons:

* We need to be able to represent keyed encoding and decoding containers 
as abstract types which are generic on a key type
* There are two ways to support abstraction in this way: protocol & type 
constraints, and generic types
   * Since Swift protocols are not generic, we unfortunately cannot 
write `protocol KeyedEncodingContainer<Key : CodingKey> { ... }`, which 
is the "ideal" version of what we're trying to represent
* Let's try this with a protocol first (simplified here):

   ```swift
   protocol Container {
         associatedtype Key : CodingKey
   }

   func container<Key : CodingKey, Cont : Container>(_ type: Key.Type) 
-> Cont where Cont.Key == Key {
         // return something
   }
   ```

   This looks promising so far — let's try to make it concrete:

   ```swift
   struct ConcreteContainer<K : CodingKey> : Container {
         typealias Key = K
   }

   func container<Key : CodingKey, Cont : Container>(_ type: Key.Type) 
-> Cont where Cont.Key == Key {
         return ConcreteContainer<Key>() // error: Cannot convert return 
expression of type 'ConcreteContainer<Key>' to return type 'Cont'
   }
   ```

   Joe or anyone from the Swift team can describe this better, but this 
is my poor-man's explanation of why this happens. Swift's type 
constraints are "directional" in a sense. You can constrain a type going 
_into_ a function, but not _out of_ a function. There is no type I could 
return from inside of `container()` which would satisfy this constraint, 
because the constraint can only be satisfied by turning `Cont` into a 
concrete type from the _outside_.

   Okay, well let's try this:

   ```swift
   func container... {
         return ConcreteContainer<Key>() as! Cont
   }
   ```

   This compiles fine! Hmm, let's try to use it:

   ```swift
   container(Int.self) // error: Generic parameter 'Cont' could not be 
inferred
   ```

   The type constraint can only be fulfilled from the outside, not the 
inside. The function call itself has no context for the concrete type 
that this would return, so this is a no-go.

* If we can't do it with type constraints in this way, is it possible 
with generic types? Yep! Generic types satisfy this without a problem. 
However, since we don't have generic protocols, we have to use a generic 
abstract base class to represent the same concept — an abstract 
container generic on the type of key which dynamically dispatches to the 
"real" subclassed type

Hopes that gives some simplified insight into the nature of this 
decision.

On 15 Mar 2017, at 18:18, Joe Groff wrote:

> Congrats on getting this out! A question from the field:
>
> https://twitter.com/mdiep/status/842178457115230210 Why does the Swift 
> Serialization API proposal use abstract base classes?
>
> -Joe
>
>
>> On Mar 15, 2017, at 3:40 PM, Itai Ferber via swift-evolution 
>> <swift-evolution at swift.org> wrote:
>>
>> Hi everyone,
>>
>> The following introduces a new Swift-focused archival and 
>> serialization API as part of the Foundation framework. We’re 
>> interested in improving the experience and safety of performing 
>> archival and serialization, and are happy to receive community 
>> feedback on this work.
>> Because of the length of this proposal, the Appendix and Alternatives 
>> Considered sections have been omitted here, but are available in the 
>> full proposal on the swift-evolution repo. The full proposal also 
>> includes an Unabridged API for further consideration.
>>
>> Without further ado, inlined below.
>>
>> — Itai
>>
>> Swift Archival & Serialization
>> 	• Proposal: SE-NNNN
>> 	• Author(s): Itai Ferber, Michael LeHew, Tony Parker
>> 	• Review Manager: TBD
>> 	• Status: Awaiting review
>> 	• Associated PRs:
>> 		• #8124
>> 		• #8125
>> Introduction
>> Foundation's current archival and serialization APIs (NSCoding, 
>> NSJSONSerialization, NSPropertyListSerialization, etc.), while 
>> fitting for the dynamism of Objective-C, do not always map optimally 
>> into Swift. This document lays out the design of an updated API that 
>> improves the developer experience of performing archival and 
>> serialization in Swift.
>>
>> Specifically:
>>
>> 	• It aims to provide a solution for the archival of Swift struct 
>> and enum types
>> 	• It aims to provide a more type-safe solution for serializing to 
>> external formats, such as JSON and plist
>> Motivation
>> The primary motivation for this proposal is the inclusion of native 
>> Swift enum and struct types in archival and serialization. Currently, 
>> developers targeting Swift cannot participate in NSCoding without 
>> being willing to abandon enum and structtypes — NSCoding is an 
>> @objc protocol, conformance to which excludes non-class types. This 
>> is can be limiting in Swift because small enums and structs can be an 
>> idiomatic approach to model representation; developers who wish to 
>> perform archival have to either forgo the Swift niceties that 
>> constructs like enumsprovide, or provide an additional compatibility 
>> layer between their "real" types and their archivable types.
>>
>> Secondarily, we would like to refine Foundation's existing 
>> serialization APIs (NSJSONSerialization and 
>> NSPropertyListSerialization) to better match Swift's strong type 
>> safety. From experience, we find that the conversion from the 
>> unstructured, untyped data of these formats into strongly-typed data 
>> structures is a good fit for archival mechanisms, rather than taking 
>> the less safe approach that 3rd-party JSON conversion approaches have 
>> taken (described further in an appendix below).
>>
>> We would like to offer a solution to these problems without 
>> sacrificing ease of use or type safety.
>>
>> Agenda
>> This proposal is the first stage of three that introduce different 
>> facets of a whole Swift archival and serialization API:
>>
>> 	• This proposal describes the basis for this API, focusing on the 
>> protocols that users adopt and interface with
>> 	• The next stage will propose specific API for new encoders
>> 	• The final stage will discuss how this new API will interop with 
>> NSCoding as it is today
>> SE-NNNN provides stages 2 and 3.
>>
>> Proposed solution
>> We will be introducing the following new types:
>>
>> 	• protocol Codable: Adopted by types to opt into archival. 
>> Conformance may be automatically derived in cases where all 
>> properties are also Codable.
>> 	• protocol CodingKey: Adopted by types used as keys for keyed 
>> containers, replacing String keys with semantic types. Conformance 
>> may be automatically derived in most cases.
>> 	• protocol Encoder: Adopted by types which can take Codable values 
>> and encode them into a native format.
>> 		• class KeyedEncodingContainer<Key : CodingKey>: Subclasses of 
>> this type provide a concrete way to store encoded values by 
>> CodingKey. Types adopting Encoder should provide subclasses of 
>> KeyedEncodingContainer to vend.
>> 		• protocol SingleValueEncodingContainer: Adopted by types which 
>> provide a concrete way to store a single encoded value. Types 
>> adopting Encoder should provide types conforming to 
>> SingleValueEncodingContainer to vend (but in many cases will be able 
>> to conform to it themselves).
>> 	• protocol Decoder: Adopted by types which can take payloads in a 
>> native format and decode Codable values out of them.
>> 		• class KeyedDecodingContainer<Key : CodingKey>: Subclasses of 
>> this type provide a concrete way to retrieve encoded values from 
>> storage by CodingKey. Types adopting Decoder should provide 
>> subclasses of KeyedDecodingContainer to vend.
>> 		• protocol SingleValueDecodingContainer: Adopted by types which 
>> provide a concrete way to retrieve a single encoded value from 
>> storage. Types adopting Decoder should provide types conforming to 
>> SingleValueDecodingContainer to vend (but in many cases will be able 
>> to conform to it themselves).
>> For end users of this API, adoption will primarily involve the 
>> Codable and CodingKey protocols. In order to participate in this new 
>> archival system, developers must add Codable conformance to their 
>> types:
>>
>> // If all properties are Codable, implementation is automatically 
>> derived:
>> public struct Location : Codable {
>>
>>
>> public let latitude: Double
>>
>>
>> public let longitude: Double
>> }
>>
>>
>>
>> public enum Animal : Int, Codable {
>>
>>
>> case chicken = 1
>>
>>
>> case
>>  dog
>>
>> case
>>  turkey
>>
>> case
>>  cow
>>
>> }
>>
>>
>>
>> public struct Farm : Codable {
>>
>>
>> public let name: String
>>
>>
>> public let location: Location
>>
>>
>> public let animals: [Animal]
>> }
>> With developer participation, we will offer encoders and decoders 
>> (described in SE-NNNN, not here) that take advantage of this 
>> conformance to offer type-safe serialization of user models:
>>
>> let farm = Farm(name: "Old MacDonald's Farm",
>>
>>                 location
>> : Location(latitude: 51.621648, longitude: 0.269273),
>>
>>                 animals
>> : [.chicken, .dog, .cow, .turkey, .dog, .chicken, .cow, .turkey, 
>> .dog])
>> let payload: Data = try JSONEncoder().encode(farm)
>>
>>
>>
>> do {
>>
>>
>> let farm = try JSONDecoder().decode(Farm.self, from: payload)
>>
>>
>>
>> // Extracted as user types:
>>
>>
>> let coordinates = "\(farm.location.latitude, 
>> farm.location.longitude)"
>> } catch {
>>
>>
>> // Encountered error during deserialization
>> }
>> This gives developers access to their data in a type-safe manner and 
>> a recognizable interface.
>>
>> Detailed design
>> To support user types, we expose the Codable protocol:
>>
>> /// Conformance to `Codable` indicates that a type can marshal itself 
>> into and out of an external representation.
>> public protocol Codable {
>>
>>
>> /// Initializes `self` by decoding from `decoder`.
>>
>>
>> ///
>>
>>
>> /// - parameter decoder: The decoder to read data from.
>>
>>
>> /// - throws: An error if reading from the decoder fails, or if read 
>> data is corrupted or otherwise invalid.
>>
>>
>> init(from decoder: Decoder) throws
>>
>>
>>
>> /// Encodes `self` into the given encoder.
>>
>>
>> ///
>>
>>
>> /// If `self` fails to encode anything, `encoder` will encode an 
>> empty `.default` container in its place.
>>
>>
>> ///
>>
>>
>> /// - parameter encoder: The encoder to write data to.
>>
>>
>> /// - throws: An error if any values are invalid for `encoder`'s 
>> format.
>>
>>
>> func encode(to encoder: Encoder) throws
>> }
>> By adopting Codable, user types opt in to this archival system.
>>
>> Structured types (i.e. types which encode as a collection of 
>> properties) encode and decode their properties in a keyed manner. 
>> Keys may be String-convertible or Int-convertible (or both), and user 
>> types which have properties should declare semantic key enums which 
>> map keys to their properties. Keys must conform to the CodingKey 
>> protocol:
>>
>> /// Conformance to `CodingKey` indicates that a type can be used as a 
>> key for encoding and decoding.
>> public protocol CodingKey {
>>
>>
>> /// The string to use in a named collection (e.g. a string-keyed 
>> dictionary).
>>
>>
>> var stringValue: String? { get }
>>
>>
>>
>> /// Initializes `self` from a string.
>>
>>
>> ///
>>
>>
>> /// - parameter stringValue: The string value of the desired key.
>>
>>
>> /// - returns: An instance of `Self` from the given string, or `nil` 
>> if the given string does not correspond to any instance of `Self`.
>>
>>
>> init?(stringValue: String)
>>
>>
>>
>> /// The int to use in an indexed collection (e.g. an int-keyed 
>> dictionary).
>>
>>
>> var intValue: Int? { get }
>>
>>
>>
>> /// Initializes `self` from an integer.
>>
>>
>> ///
>>
>>
>> /// - parameter intValue: The integer value of the desired key.
>>
>>
>> /// - returns: An instance of `Self` from the given integer, or `nil` 
>> if the given integer does not correspond to any instance of `Self`.
>>
>>
>> init?(intValue: Int)
>> }
>> For most types, String-convertible keys are a reasonable default; for 
>> performance, however, Int-convertible keys are preferred, and 
>> Encoders may choose to make use of Ints over Strings. Framework types 
>> should provide keys which have both for flexibility and performance 
>> across different types of Encoders. It is generally an error to 
>> provide a key which has neither a stringValue nor an intValue.
>>
>> By default, CodingKey conformance can be derived for enums which have 
>> either String or Int backing:
>>
>> enum Keys1 : CodingKey {
>>
>>
>> case a // (stringValue: "a", intValue: nil)
>>
>>
>> case b // (stringValue: "b", intValue: nil)
>> }
>>
>>
>>
>> enum Keys2 : String, CodingKey {
>>
>>
>> case c = "foo" // (stringValue: "foo", intValue: nil)
>>
>>
>> case d         // (stringValue: "d", intValue: nil)
>> }
>>
>>
>>
>> enum Keys3 : Int, CodingKey {
>>
>>
>> case e = 4 // (stringValue: "e", intValue: 4)
>>
>>
>> case f     // (stringValue: "f", intValue: 5)
>>
>>
>> case g = 9 // (stringValue: "g", intValue: 9)
>> }
>> Coding keys which are not enums, have associated values, or have 
>> other raw representations must implement these methods manually.
>>
>> In addition to automatic CodingKey conformance derivation for enums, 
>> Codableconformance can be automatically derived for certain types as 
>> well:
>>
>> 	• Types whose properties are all either Codable or primitive get 
>> an automatically derived String-backed CodingKeys enum mapping 
>> properties to case names
>> 	• Types falling into (1) and types which provide a CodingKeys enum 
>> (directly or via a typealias) whose case names map to properties 
>> which are all Codableget automatic derivation of init(from:) and 
>> encode(to:) using those properties and keys. Types may choose to 
>> provide a custom init(from:) or encode(to:) (or both); whichever they 
>> do not provide will be automatically derived
>> 	• Types which fall into neither (1) nor (2) will have to provide a 
>> custom key type and provide their own init(from:) and encode(to:)
>> Many types will either allow for automatic derivation of all 
>> codability (1), or provide a custom key subset and take advantage of 
>> automatic method derivation (2).
>>
>> Encoding and Decoding
>>
>> Types which are encodable encode their data into a container provided 
>> by their Encoder:
>>
>> /// An `Encoder` is a type which can encode values into a native 
>> format for external representation.
>> public protocol Encoder {
>>
>>
>> /// Populates `self` with an encoding container (of `.default` type) 
>> and returns it, keyed by the given key type.
>>
>>
>> ///
>>
>>
>> /// - parameter type: The key type to use for the container.
>>
>>
>> /// - returns: A new keyed encoding container.
>>
>>
>> /// - precondition: May not be called after a previous 
>> `self.container(keyedBy:)` call of a different 
>> `EncodingContainerType`.
>>
>>
>> /// - precondition: May not be called after a value has been encoded 
>> through a prior `self.singleValueContainer()` call.
>>
>>
>> func container<Key : CodingKey>(keyedBy type: Key.Type) -> 
>> KeyedEncodingContainer<Key>
>>
>>
>>
>> /// Returns an encoding container appropriate for holding a single 
>> primitive value.
>>
>>
>> ///
>>
>>
>> /// - returns: A new empty single value container.
>>
>>
>> /// - precondition: May not be called after a prior 
>> `self.container(keyedBy:)` call.
>>
>>
>> /// - precondition: May not be called after a value has been encoded 
>> through a previous `self.singleValueContainer()` call.
>>
>>
>> func singleValueContainer() -> SingleValueEncodingContainer
>>
>>
>>
>> /// The path of coding keys taken to get to this point in encoding.
>>
>>
>> var codingKeyContext: [CodingKey] { get }
>> }
>>
>>
>>
>> // Continuing examples from before; below is automatically generated 
>> by the compiler if no customization is needed.
>> public struct Location : Codable {
>>
>>
>> private enum CodingKeys : CodingKey {
>>
>>
>> case
>>  latitutude
>>
>> case
>>  longitude
>>
>> }
>>
>>
>>
>> public func encode(to encoder: Encoder) throws {
>>
>>
>> // Generic keyed encoder gives type-safe key access: cannot encode 
>> with keys of the wrong type.
>>
>>
>> let container = encoder.container(keyedBy: CodingKeys.self)
>>
>>
>>
>> // The encoder is generic on the key -- free key autocompletion here.
>>
>>
>> try container.encode(latitude, forKey: .latitude)
>>
>>
>> try container.encode(longitude, forKey: .longitude)
>>
>>
>> }
>> }
>>
>>
>>
>> public struct Farm : Codable {
>>
>>
>> private enum CodingKeys : CodingKey {
>>
>>
>> case
>>  name
>>
>> case
>>  location
>>
>> case
>>  animals
>>
>> }
>>
>>
>>
>> public func encode(to encoder: Encoder) throws {
>>
>>
>> let container = encoder.container(keyedBy: CodingKeys.self)
>>
>>
>> try container.encode(name, forKey: .name)
>>
>>
>> try container.encode(location, forKey: .location)
>>
>>
>> try container.encode(animals, forKey: .animals)
>>
>>
>> }
>> }
>> Similarly, decodable types initialize from data read from their 
>> Decoder's container:
>>
>> /// A `Decoder` is a type which can decode values from a native 
>> format into in-memory representations.
>> public protocol Decoder {
>>
>>
>> /// Returns the data stored in `self` as represented in a container 
>> keyed by the given key type.
>>
>>
>> ///
>>
>>
>> /// - parameter type: The key type to use for the container.
>>
>>
>> /// - returns: A keyed decoding container view into `self`.
>>
>>
>> /// - throws: `CocoaError.coderTypeMismatch` if the encountered 
>> stored value is not a keyed container.
>>
>>
>> func container<Key : CodingKey>(keyedBy type: Key.Type) throws -> 
>> KeyedDecodingContainer<Key>
>>
>>
>>
>> /// Returns the data stored in `self` as represented in a container 
>> appropriate for holding a single primitive value.
>>
>>
>> ///
>>
>>
>> /// - returns: A single value container view into `self`.
>>
>>
>> /// - throws: `CocoaError.coderTypeMismatch` if the encountered 
>> stored value is not a single value container.
>>
>>
>> func singleValueContainer() throws -> SingleValueDecodingContainer
>>
>>
>>
>> /// The path of coding keys taken to get to this point in decoding.
>>
>>
>> var codingKeyContext: [CodingKey] { get }
>> }
>>
>>
>>
>> // Continuing examples from before; below is automatically generated 
>> by the compiler if no customization is needed.
>> public struct Location : Codable {
>>
>>
>> public init(from decoder: Decoder) throws {
>>
>>
>> let container = try decoder.container(keyedBy: CodingKeys.self)
>>
>>         latitude
>> = try container.decode(Double.self, forKey: .latitude)
>>
>>         longitude
>> = try container.decode(Double.self, forKey: .longitude)
>>
>>
>> }
>> }
>>
>>
>>
>> public struct Farm : Codable {
>>
>>
>> public init(from decoder: Decoder) throws {
>>
>>
>> let container = try decoder.container(keyedBy: CodingKeys.self)
>>
>>         name
>> = try container.decode(String.self, forKey: .name)
>>
>>         location
>> = try container.decode(Location.self, forKey: .location)
>>
>>         animals
>> = try container.decode([Animal].self, forKey: .animals)
>>
>>
>> }
>> }
>> Keyed Encoding Containers
>>
>> Keyed encoding containers are the primary interface that most Codable 
>> types interact with for encoding and decoding. Through these, Codable 
>> types have strongly-keyed access to encoded data by using keys that 
>> are semantically correct for the operations they want to express.
>>
>> Since semantically incompatible keys will rarely (if ever) share the 
>> same key type, it is impossible to mix up key types within the same 
>> container (as is possible with Stringkeys), and since the type is 
>> known statically, keys get autocompletion by the compiler.
>>
>> /// `KeyedEncodingContainer` is a generic abstract base class that 
>> provides a view into an `Encoders` storage and is used to hold the 
>> encoded properties of a `Codable` type.
>> ///
>> /// Encoders should provide subclasses of `KeyedEncodingContainer` 
>> for their format.
>>
>> open
>> class KeyedEncodingContainer<Key : CodingKey> {
>>
>>
>> /// Encodes the given value for the given key.
>>
>>
>> ///
>>
>>
>> /// - parameter value: The value to encode.
>>
>>
>> /// - parameter key: The key to associate the value with.
>>
>>
>> /// - throws: `CocoaError.coderInvalidValue` if the given value is 
>> invalid in the current context for this format.
>>
>>
>> /// - precondition: The key must have a `stringValue` or `intValue` 
>> appropriate for the encoding container type.
>>
>>     open
>> func encode<Value : Codable>(_ value: Value?, forKey key: Key) throws
>>
>>
>>
>> /// Encodes the given value for the given key.
>>
>>
>> ///
>>
>>
>> /// - parameter value: The value to encode.
>>
>>
>> /// - parameter key: The key to associate the value with.
>>
>>
>> /// - throws: `CocoaError.coderInvalidValue` if the given value is 
>> invalid in the current context for this format.
>>
>>
>> /// - precondition: The key must have a `stringValue` or `intValue` 
>> appropriate for the encoding container type.
>>
>>     open
>> func encode(_ value: Bool?,   forKey key: Key) throws
>>
>>     open
>> func encode(_ value: Int?,    forKey key: Key) throws
>>
>>     open
>> func encode(_ value: Int8?,   forKey key: Key) throws
>>
>>     open
>> func encode(_ value: Int16?,  forKey key: Key) throws
>>
>>     open
>> func encode(_ value: Int32?,  forKey key: Key) throws
>>
>>     open
>> func encode(_ value: Int64?,  forKey key: Key) throws
>>
>>     open
>> func encode(_ value: UInt?,   forKey key: Key) throws
>>
>>     open
>> func encode(_ value: UInt8?,  forKey key: Key) throws
>>
>>     open
>> func encode(_ value: UInt16?, forKey key: Key) throws
>>
>>     open
>> func encode(_ value: UInt32?, forKey key: Key) throws
>>
>>     open
>> func encode(_ value: UInt64?, forKey key: Key) throws
>>
>>     open
>> func encode(_ value: Float?,  forKey key: Key) throws
>>
>>     open
>> func encode(_ value: Double?, forKey key: Key) throws
>>
>>     open
>> func encode(_ value: String?, forKey key: Key) throws
>>
>>     open
>> func encode(_ value: Data?,   forKey key: Key) throws
>>
>>
>>
>> /// Encodes the given object weakly for the given key.
>>
>>
>> ///
>>
>>
>> /// For `Encoder`s that implement this functionality, this will only 
>> encode the given object and associate it with the given key if it 
>> encoded unconditionally elsewhere in the archive (either previously 
>> or in the future).
>>
>>
>> ///
>>
>>
>> /// For formats which don't support this feature, the default 
>> implementation encodes the given object unconditionally.
>>
>>
>> ///
>>
>>
>> /// - parameter object: The object to encode.
>>
>>
>> /// - parameter key: The key to associate the object with.
>>
>>
>> /// - throws: `CocoaError.coderInvalidValue` if the given value is 
>> invalid in the current context for this format.
>>
>>
>> /// - precondition: The key must have a `stringValue` or `intValue` 
>> appropriate for the encoding container type.
>>
>>     open
>> func encodeWeak<Object : AnyObject & Codable>(_ object: Object?, 
>> forKey key: Key) throws
>>
>>
>>
>> /// The path of coding keys taken to get to this point in encoding.
>>
>>     open
>> var codingKeyContext: [CodingKey]
>> }
>>
>>
>>
>> /// `KeyedDecodingContainer` is a generic abstract base class that 
>> provides a view into an `Decoders` storage and is used to hold the 
>> encoded properties of a `Codable` type.
>> ///
>> /// Decoders should provide subclasses of `KeyedDecodingContainer` 
>> for their format.
>>
>> open
>> class KeyedDecodingContainer<Key : CodingKey> {
>>
>>
>> /// All the keys the `Decoder` has for this container.
>>
>>
>> ///
>>
>>
>> /// Different keyed containers from the same `Decoder` may return 
>> different keys here; it is possible to encode with multiple key types 
>> which are not convertible to one another. This should report all keys 
>> present which are convertible to the requested type.
>>
>>     open
>> var allKeys: [Key]
>>
>>
>>
>> /// Returns whether the `Decoder` contains a value associated with 
>> the given key.
>>
>>
>> ///
>>
>>
>> /// The value associated with the given key may be a null value as 
>> appropriate for the data format.
>>
>>
>> ///
>>
>>
>> /// - parameter key: The key to search for.
>>
>>
>> /// - returns: Whether the `Decoder` has an entry for the given key.
>>
>>     open
>> func contains(_ key: Key) -> Bool
>>
>>
>>
>> /// Decodes a value of the given type for the given key.
>>
>>
>> ///
>>
>>
>> /// A default implementation is given for these types which calls 
>> into the abstract `decodeIfPresent` implementations below.
>>
>>
>> ///
>>
>>
>> /// - parameter type: The type of value to decode.
>>
>>
>> /// - parameter key: The key that the decoded value is associated 
>> with.
>>
>>
>> /// - returns: A value of the requested type, if present for the 
>> given key and convertible to the requested type.
>>
>>
>> /// - throws: `CocoaError.coderTypeMismatch` if the encountered 
>> encoded value is not convertible to the requested type.
>>
>>
>> /// - throws: `CocoaError.coderValueNotFound` if `self` does not have 
>> an entry for the given key or if the value is null.
>>
>>     open
>> func decode(_ type: Bool.Type,   forKey key: Key) throws -> Bool
>>
>>     open
>> func decode(_ type: Int.Type,    forKey key: Key) throws -> Int
>>
>>     open
>> func decode(_ type: Int8.Type,   forKey key: Key) throws -> Int8
>>
>>     open
>> func decode(_ type: Int16.Type,  forKey key: Key) throws -> Int16
>>
>>     open
>> func decode(_ type: Int32.Type,  forKey key: Key) throws -> Int32
>>
>>     open
>> func decode(_ type: Int64.Type,  forKey key: Key) throws -> Int64
>>
>>     open
>> func decode(_ type: UInt.Type,   forKey key: Key) throws -> UInt
>>
>>     open
>> func decode(_ type: UInt8.Type,  forKey key: Key) throws -> UInt8
>>
>>     open
>> func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16
>>
>>     open
>> func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32
>>
>>     open
>> func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64
>>
>>     open
>> func decode(_ type: Float.Type,  forKey key: Key) throws -> Float
>>
>>     open
>> func decode(_ type: Double.Type, forKey key: Key) throws -> Double
>>
>>     open
>> func decode(_ type: String.Type, forKey key: Key) throws -> String
>>
>>     open
>> func decode(_ type: Data.Type,   forKey key: Key) throws -> Data
>>
>>     open
>> func decode<Value : Codable>(_ type: Value.Type, forKey key: Key) 
>> throws -> Value
>>
>>
>>
>> /// Decodes a value of the given type for the given key, if present.
>>
>>
>> ///
>>
>>
>> /// This method returns `nil` if the container does not have a value 
>> associated with `key`, or if the value is null. The difference 
>> between these states can be disambiguated with a `contains(_:)` call.
>>
>>
>> ///
>>
>>
>> /// - parameter type: The type of value to decode.
>>
>>
>> /// - parameter key: The key that the decoded value is associated 
>> with.
>>
>>
>> /// - returns: A decoded value of the requested type, or `nil` if the 
>> `Decoder` does not have an entry associated with the given key, or if 
>> the value is a null value.
>>
>>
>> /// - throws: `CocoaError.coderTypeMismatch` if the encountered 
>> encoded value is not convertible to the requested type.
>>
>>     open
>> func decodeIfPresent(_ type: Bool.Type,   forKey key: Key) throws -> 
>> Bool?
>>
>>     open
>> func decodeIfPresent(_ type: Int.Type,    forKey key: Key) throws -> 
>> Int?
>>
>>     open
>> func decodeIfPresent(_ type: Int8.Type,   forKey key: Key) throws -> 
>> Int8?
>>
>>     open
>> func decodeIfPresent(_ type: Int16.Type,  forKey key: Key) throws -> 
>> Int16?
>>
>>     open
>> func decodeIfPresent(_ type: Int32.Type,  forKey key: Key) throws -> 
>> Int32?
>>
>>     open
>> func decodeIfPresent(_ type: Int64.Type,  forKey key: Key) throws -> 
>> Int64?
>>
>>     open
>> func decodeIfPresent(_ type: UInt.Type,   forKey key: Key) throws -> 
>> UInt?
>>
>>     open
>> func decodeIfPresent(_ type: UInt8.Type,  forKey key: Key) throws -> 
>> UInt8?
>>
>>     open
>> func decodeIfPresent(_ type: UInt16.Type, forKey key: Key) throws -> 
>> UInt16?
>>
>>     open
>> func decodeIfPresent(_ type: UInt32.Type, forKey key: Key) throws -> 
>> UInt32?
>>
>>     open
>> func decodeIfPresent(_ type: UInt64.Type, forKey key: Key) throws -> 
>> UInt64?
>>
>>     open
>> func decodeIfPresent(_ type: Float.Type,  forKey key: Key) throws -> 
>> Float?
>>
>>     open
>> func decodeIfPresent(_ type: Double.Type, forKey key: Key) throws -> 
>> Double?
>>
>>     open
>> func decodeIfPresent(_ type: String.Type, forKey key: Key) throws -> 
>> String?
>>
>>     open
>> func decodeIfPresent(_ type: Data.Type,   forKey key: Key) throws -> 
>> Data?
>>
>>     open
>> func decodeIfPresent<Value : Codable>(_ type: Value.Type, forKey key: 
>> Key) throws -> Value?
>>
>>
>>
>> /// The path of coding keys taken to get to this point in decoding.
>>
>>     open
>> var codingKeyContext: [CodingKey]
>> }
>> These encode(_:forKey:) and decode(_:forKey:) overloads give strong, 
>> static type guarantees about what is encodable (preventing accidental 
>> attempts to encode an invalid type), and provide a list of primitive 
>> types which are common to all encoders and decoders that users can 
>> rely on.
>>
>> Coming in Swift 4 is the ability to express that "a collection of 
>> things which are Codable is Codable" (conditional conformance), 
>> allowing collections which we extend (Array, Dictionary, etc.) to 
>> fall into these overloads as well.
>>
>> Encoding Container Types
>>
>> For some types, the container into which they encode has meaning. 
>> Especially when coding for a specific output format (e.g. when 
>> communicating with a JSON API), a type may wish to explicitly encode 
>> as an array or a dictionary:
>>
>> // Continuing from before
>> public protocol Encoder {
>>
>>
>> /// Populates `self` with an encoding container of the given type and 
>> returns it, keyed by the given key type.
>>
>>
>> ///
>>
>>
>> /// A default implementation of `Encoder.container(keyedBy:)` calls 
>> this method with a container type of `.default`.
>>
>>
>> ///
>>
>>
>> /// - parameter keyType: The key type to use for the container.
>>
>>
>> /// - parameter containerType: The container type to create.
>>
>>
>> /// - returns: A new keyed encoding container.
>>
>>
>> /// - precondition: May not be called after a previous 
>> `self.container(keyedBy:)` call of a different 
>> `EncodingContainerType`.
>>
>>
>> /// - precondition: May not be called after a value has been encoded 
>> through a prior `self.singleValueContainer()` call.
>>
>>
>> func container<Key : CodingKey>(keyedBy keyType: Key.Type, type 
>> containerType: EncodingContainerType) -> KeyedEncodingContainer<Key>
>> }
>>
>>
>>
>> /// An `EncodingContainerType` specifies the type of container an 
>> `Encoder` should use to store values.
>> public enum EncodingContainerType {
>>
>>
>> /// The `Encoder`'s preferred container type; equivalent to either 
>> `.array` or `.dictionary` as appropriate for the encoder.
>>
>>
>> case `default
>> `
>>
>>
>> /// Explicitly requests the use of an array to store encoded values.
>>
>>
>> case
>>  array
>>
>>
>> /// Explicitly requests the use of a dictionary to store encoded 
>> values.
>>
>>
>> case
>>  dictionary
>>
>> }
>> Single Value Containers
>>
>> For other types, an array or dictionary container may not even make 
>> sense (e.g. values which are RawRepresentable as a single primitive 
>> value). Those types may encode and decode directly as a single value, 
>> instead of requesting a keyed container:
>>
>> /// A `SingleValueEncodingContainer` is a container which can support 
>> the storage and direct encoding of a single non-keyed value.
>> public protocol SingleValueEncodingContainer {
>>
>>
>> /// Encodes a single value of the given type.
>>
>>
>> ///
>>
>>
>> /// - parameter value: The value to encode.
>>
>>
>> /// - throws: `CocoaError.coderInvalidValue` if the given value is 
>> invalid in the current context for this format.
>>
>>
>> /// - precondition: May not be called after a previous 
>> `self.encode(_:)` call.
>>
>>
>> func encode(_ value: Bool) throws
>>
>>
>> func encode(_ value: Int) throws
>>
>>
>> func encode(_ value: Int8) throws
>>
>>
>> func encode(_ value: Int16) throws
>>
>>
>> func encode(_ value: Int32) throws
>>
>>
>> func encode(_ value: Int64) throws
>>
>>
>> func encode(_ value: UInt) throws
>>
>>
>> func encode(_ value: UInt8) throws
>>
>>
>> func encode(_ value: UInt16) throws
>>
>>
>> func encode(_ value: UInt32) throws
>>
>>
>> func encode(_ value: UInt64) throws
>>
>>
>> func encode(_ value: Float) throws
>>
>>
>> func encode(_ value: Double) throws
>>
>>
>> func encode(_ value: String) throws
>>
>>
>> func encode(_ value: Data) throws
>> }
>>
>>
>>
>> /// A `SingleValueDecodingContainer` is a container which can support 
>> the storage and direct decoding of a single non-keyed value.
>> public protocol SingleValueDecodingContainer {
>>
>>
>> /// Decodes a single value of the given type.
>>
>>
>> ///
>>
>>
>> /// - parameter type: The type to decode as.
>>
>>
>> /// - returns: A value of the requested type.
>>
>>
>> /// - throws: `CocoaError.coderTypeMismatch` if the encountered 
>> encoded value cannot be converted to the requested type.
>>
>>
>> func decode(_ type: Bool.Type) throws -> Bool
>>
>>
>> func decode(_ type: Int.Type) throws -> Int
>>
>>
>> func decode(_ type: Int8.Type) throws -> Int8
>>
>>
>> func decode(_ type: Int16.Type) throws -> Int16
>>
>>
>> func decode(_ type: Int32.Type) throws -> Int32
>>
>>
>> func decode(_ type: Int64.Type) throws -> Int64
>>
>>
>> func decode(_ type: UInt.Type) throws -> UInt
>>
>>
>> func decode(_ type: UInt8.Type) throws -> UInt8
>>
>>
>> func decode(_ type: UInt16.Type) throws -> UInt16
>>
>>
>> func decode(_ type: UInt32.Type) throws -> UInt32
>>
>>
>> func decode(_ type: UInt64.Type) throws -> UInt64
>>
>>
>> func decode(_ type: Float.Type) throws -> Float
>>
>>
>> func decode(_ type: Double.Type) throws -> Double
>>
>>
>> func decode(_ type: String.Type) throws -> String
>>
>>
>> func decode(_ type: Data.Type) throws -> Data
>> }
>>
>>
>>
>> // Continuing example from before; below is automatically generated 
>> by the compiler if no customization is needed.
>> public enum Animal : Int, Codable {
>>
>>
>> public func encode(to encoder: Encoder) throws {
>>
>>
>> // Encode as a single value; no keys.
>>
>>
>> try encoder.singleValueContainer.encode(self.rawValue)
>>
>>
>> }
>>
>>
>>
>> public init(from decoder: Decoder) throws {
>>
>>
>> // Decodes as a single value; no keys.
>>
>>
>> let intValue = try decoder.singleValueContainer().decode(Int.self)
>>
>>
>> if let value = Self(rawValue: intValue) {
>>
>>
>> self =
>>  value
>>
>> } else {
>>
>>
>> throw CocoaError.error(.coderReadCorrupt)
>>
>>
>> }
>>
>>
>> }
>> }
>> In the example given above, since Animal uses a single value 
>> container, [.chicken, .dog, .cow, .turkey, .dog, .chicken, .cow, 
>> .turkey, .dog]would encode directly as [1, 2, 4, 3, 2, 1, 4, 3, 2].
>>
>> Nesting
>>
>> In practice, some types may also need to control how data is nested 
>> within their container, or potentially nest other containers within 
>> their container. Keyed containers allow this by returning nested 
>> containers of differing key types:
>>
>> // Continuing from before
>>
>> open
>> class KeyedEncodingContainer<Key : CodingKey> {
>>
>>
>> /// Stores an encoding container for the given key and returns it.
>>
>>
>> ///
>>
>>
>> /// - parameter keyType: The key type to use for the container.
>>
>>
>> /// - parameter containerType: The container type to create.
>>
>>
>> /// - parameter key: The key to encode the container for.
>>
>>
>> /// - returns: A new keyed encoding container.
>>
>>     open
>> func nestedContainer<NestedKey : CodingKey>(keyedBy keyType: 
>> NestedKey.Type, type containerType: EncodingContainerType, forKey 
>> key: Key) -> KeyedEncodingContainer<NestedKey>
>> }
>>
>>
>> open
>> class KeyedDecodingContainer<Key : CodingKey> {
>>
>>
>> /// Returns the data stored for the given key as represented in a 
>> container keyed by the given key type.
>>
>>
>> ///
>>
>>
>> /// - parameter type: The key type to use for the container.
>>
>>
>> /// - parameter key: The key that the nested container is associated 
>> with.
>>
>>
>> /// - returns: A keyed decoding container view into `self`.
>>
>>
>> /// - throws: `CocoaError.coderTypeMismatch` if the encountered 
>> stored value is not a container.
>>
>>     open
>> func nestedContainer<NestedKey : CodingKey>(keyedBy type: 
>> NestedKey.Type, forKey key: Key) throws -> 
>> KeyedDecodingContainer<NestedKey>
>> }
>> This can be common when coding against specific external data 
>> representations:
>>
>> // User type for interfacing with a specific JSON API. JSON API 
>> expects encoding as {"id": ..., "properties": {"name": ..., 
>> "timestamp": ...}}. Swift type differs from encoded type, and 
>> encoding needs to match a spec:
>> struct Record : Codable {
>>
>>
>> // We care only about these values from the JSON payload
>>
>>
>> let id: Int
>>
>>
>> let name: String
>>
>>
>> let timestamp: Double
>>
>>
>>
>> // ...
>>
>>
>>
>> private enum Keys : CodingKey {
>>
>>
>> case
>>  id
>>
>> case
>>  properties
>>
>> }
>>
>>
>>
>> private enum PropertiesKeys : CodingKey {
>>
>>
>> case
>>  name
>>
>> case
>>  timestamp
>>
>> }
>>
>>
>>
>> public func encode(to encoder: Encoder) throws {
>>
>>
>> let container = encoder.container(keyedBy: Keys.self, type: 
>> .dictionary)
>>
>>
>> try container.encode(id, forKey: .id)
>>
>>
>>
>> // Set a dictionary for the "properties" key
>>
>>
>> let nested = container.nestedContainer(keyedBy: PropertiesKeys.self, 
>> type: .dictionary, forKey: .properties)
>>
>>
>> try nested.encode(name, forKey: .name)
>>
>>
>> try nested.encode(timestamp, forKey: .timestamp)
>>
>>
>> }
>>
>>
>>
>> public init(from decoder: Decoder) throws {
>>
>>
>> let container = try decoder.container(keyedBy: Keys.self)
>>
>>         id
>> = try container.decode(Int.self, forKey: .id)
>>
>>
>>
>> let nested = try container.nestedContainer(keyedBy: 
>> PropertiesKeys.self, forKey: .properties)
>>
>>         name
>> = try nested.decode(String.self, forKey: .name)
>>
>>         timestamp
>> = try nested.decode(Double.self, forKey: .timestamp)
>>
>>
>> }
>> }
>> Inheritance
>>
>> Inheritance in this system is supported much like it is with NSCoding 
>> — on encoding, objects which inherit from a type that is Codable 
>> encode super using their encoder, and pass a decoder to 
>> super.init(from:) on decode. With the existing NSCoding API, this is 
>> most often done like so, by convention:
>>
>> - (void)encodeWithCoder:(NSCoder *)encoder {
>>
>>
>> [super encodeWithCoder:encoder];
>>
>>
>> // ... encode properties
>> }
>>
>>
>>
>> - (instancetype)initWithCoder:(NSCoder *)decoder {
>>
>>
>> if ((self = [super initWithCoder:decoder])) {
>>
>>
>> // ... decode properties
>>
>>
>> }
>>
>>
>>
>> return self;
>> }
>> In practice, this approach means that the properties of self and the 
>> properties of super get encoded into the same container: if self 
>> encodes values for keys "a", "b", and "c", and super encodes "d", 
>> "e", and "f", the resulting object is archived as {"a": ..., "b": 
>> ..., "c": ..., "d": ..., "e": ..., "f": ...}. This approach has two 
>> drawbacks:
>>
>> 	• Things which self encodes may overwrite super's (or vice versa, 
>> depending on when -[super encodeWithCoder:] is called
>> 	• self and super may not encode into different container types 
>> (e.g. self in a sequential fashion, and super in a keyed fashion)
>> The second point is not an issue for NSKeyedArchiver, since all 
>> values encode with keys (sequentially coded elements get 
>> autogenerated keys). This proposed API, however, allows for self and 
>> super to explicitly request conflicting containers (.arrayand 
>> .dictionary, which may not be mixed, depending on the data format).
>>
>> To remedy both of these points, we adopt a new convention for 
>> inheritance-based coding — encoding super as a sub-object of self:
>>
>> public class MyCodable : SomethingCodable {
>>
>>
>> public func encode(to encoder: Encoder) throws {
>>
>>
>> let container = encoder.container(keyedBy: CodingKeys.self)
>>
>>
>> // ... encode some properties
>>
>>
>>
>> // superEncoder() gives `super` a nested container to encode into 
>> (for
>>
>>
>> // a predefined key).
>>
>>
>> try super.encode(to: container.superEncoder())
>>
>>
>> }
>>
>>
>>
>> public init(from decoder: Decoder) throws {
>>
>>
>> let container = try decoder.container(keyedBy: CodingKeys.self)
>>
>>
>> // ... decode some properties
>>
>>
>>
>> // Allow `super` to decode from the nested container.
>>
>>
>> try super.init(from: container.superDecoder())
>>
>>
>> }
>> }
>> If a shared container is desired, it is still possible to call 
>> super.encode(to: encoder) and super.init(from: decoder), but we 
>> recommend the safer containerized option.
>>
>> superEncoder() and superDecoder() are provided on 
>> KeyedEncodingContainer and KeyedDecodingContainer to provide handles 
>> to containers for super to use. While users may specify a custom key 
>> to encode super with, the default behavior is to use a key with a 
>> stringValue of "super" and an intValue of 0:
>>
>> // Continuing from before
>>
>> open
>> class KeyedEncodingContainer<Key : CodingKey> {
>>
>>
>> /// Stores a new nested container for the default `super` key and 
>> returns a new `Encoder` instance for encoding `super` into that 
>> container.
>>
>>
>> ///
>>
>>
>> /// Equivalent to calling `superEncoder(forKey:)` with 
>> `Key(stringValue: "super", intValue: 0)`.
>>
>>
>> ///
>>
>>
>> /// - returns: A new `Encoder` to pass to `super.encode(to:)`.
>>
>>     open
>> func superEncoder() -> Encoder
>>
>>
>>
>> /// Stores a new nested container for the given key and returns a new 
>> `Encoder` instance for encoding `super` into that container.
>>
>>
>> ///
>>
>>
>> /// - parameter key: The key to encode `super` for.
>>
>>
>> /// - returns: A new `Encoder` to pass to `super.encode(to:)`.
>>
>>
>> /// - precondition: The key must have a `stringValue` or `intValue` 
>> appropriate for the encoding container type.
>>
>>     open
>> func superEncoder(forKey key: Key) -> Encoder
>> }
>>
>>
>> open
>> class KeyedDecodingContainer<Key : CodingKey> {
>>
>>
>> /// Returns a `Decoder` instance for decoding `super` from the 
>> container associated with the default `super` key.
>>
>>
>> ///
>>
>>
>> /// Equivalent to calling `superDecoder(forKey:)` with 
>> `Key(stringValue: "super", intValue: 0)`.
>>
>>
>> ///
>>
>>
>> /// - returns: A new `Decoder` to pass to `super.init(from:)`.
>>
>>
>> /// - throws: `CocoaError.coderValueNotFound` if `self` does not have 
>> an entry for the default `super` key, or if the stored value is null.
>>
>>     open
>> func superDecoder() throws -> Decoder
>>
>>
>>
>> /// Returns a `Decoder` instance for decoding `super` from the 
>> container associated with the given key.
>>
>>
>> ///
>>
>>
>> /// - parameter key: The key to decode `super` for.
>>
>>
>> /// - returns: A new `Decoder` to pass to `super.init(from:)`.
>>
>>
>> /// - throws: `CocoaError.coderValueNotFound` if `self` does not have 
>> an entry for the given key, or if the stored value is null.
>>
>>     open
>> func superDecoder(forKey key: Key) throws -> Decoder
>> }
>> Primitive Codable Conformance
>>
>> The encoding container types offer overloads for working with and 
>> processing the API's primitive types (String, Int, Double, etc.). 
>> However, for ease of implementation (both in this API and others), it 
>> can be helpful for these types to conform to Codable themselves. 
>> Thus, along with these overloads, we will offer Codableconformance on 
>> these types:
>>
>> extension Bool : Codable {
>>
>>
>> public init(from decoder: Decoder) throws {
>>
>>
>> self = try decoder.singleValueContainer().decode(Bool.self)
>>
>>
>> }
>>
>>
>>
>> public func encode(to encoder: Encoder) throws {
>>
>>
>> try encoder.singleValueContainer().encode( self)
>>
>>
>> }
>> }
>>
>>
>>
>> // Repeat for others...
>> This conformance allows one to write functions which accept Codable 
>> types without needing specific overloads for the fifteen primitive 
>> types as well. This also simplifies conditional conformance (e.g. 
>> expressing "extension Array : Codable where Element : Codable") by 
>> removing the need for additional explicit conformances for these 
>> types.
>>
>> Since Swift's function overload rules prefer more specific functions 
>> over generic functions, the specific overloads are chosen where 
>> possible (e.g. encode("Hello, world!", forKey: .greeting) will choose 
>> encode(_: String, forKey: Key) over encode<T : Codable>(_: T, forKey: 
>> Key)). This maintains performance over dispatching through the 
>> Codable existential, while allowing for the flexibility of fewer 
>> overloads where applicable.
>>
>> Additional Extensions
>>
>> Along with the primitive Codable conformance above, extensions on 
>> CodableRawRepresentable types whose RawValue is a primitive types 
>> will provide default implementations for encoding and decoding:
>>
>> public extension RawRepresentable where RawValue == Bool, Self : 
>> Codable {
>>
>>
>> public init(from decoder: Decoder) throws {
>>
>>
>> let decoded = try 
>> decoder.singleValueContainer().decode(RawValue.self)
>>
>>
>> guard let value = Self(rawValue: decoded) else {
>>
>>
>> throw CocoaError.error(.coderReadCorrupt)
>>
>>
>> }
>>
>>
>>
>> self =
>>  value
>>
>> }
>>
>>
>>
>> public func encode(to encoder: Encoder) throws {
>>
>>
>> try encoder.singleValueContainer().encode(self.rawValue)
>>
>>
>> }
>> }
>>
>>
>>
>> // Repeat for others...
>> This allows for trivial Codable conformance of enum types (and manual 
>> RawRepresentable implementations) with primitive backing.
>>
>> Source compatibility
>> This proposal is additive — existing code will not have to change 
>> due to this API addition. This implementation can be made available 
>> in both Swift 4 and the Swift 3 compatibility mode.
>>
>> Effect on ABI stability
>> The addition of this API will not be an ABI-breaking change. However, 
>> this will add limitations for changes in future versions of Swift, as 
>> parts of the API will have to remain unchanged between versions of 
>> Swift (barring some additions, discussed below).
>>
>> Effect on API resilience
>> Much like new API added to the standard library, once added, many 
>> changes to this API will be ABI- and source-breaking changes. In 
>> particular, changes which change the types or names of methods or 
>> arguments, add required methods on protocols or classes, or remove 
>> supplied default implementations will break client behavior.
>>
>> The following protocols and classes may not have methods added to 
>> them without providing default implementations:
>>
>> 	• Codable
>> 	• CodingKey
>> 	• Encoder
>> 	• SingleValueEncodingContainer
>> 	• KeyedEncodingContainer
>> 	• Decoder
>> 	• SingleValueDecodingContainer
>> 	• KeyedDecodingContainer
>> The following classes may not remove existing default 
>> implementations:
>>
>> 	• KeyedEncodingContainer
>> 	• KeyedDecodingContainer
>> Various extensions to Swift primitive types (Bool, Int, Double, etc.) 
>> and to RawRepresentable types (where RawValue == Bool, == Int, == 
>> Double, etc.) may also not be removed.
>>
>> In general, changes to the proposed types will be restricted as 
>> described in the library evolution document in the Swift repository.
>>
>> _______________________________________________
>> 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/20170315/9bdba412/attachment.html>


More information about the swift-evolution mailing list