> On 17 Mar 2017, at 21:23, Brent Royal-Gordon via swift-evolution
> <[email protected]> wrote:
>
>> On Mar 16, 2017, at 12:33 PM, Itai Ferber <[email protected]> wrote:
>> Optional values are accepted and vended directly through the API. The
>> encode(_:forKey:) methods take optional values directly, and
>> decodeIfPresent(_:forKey:) vend optional values.
>>
>> Optional is special in this way — it’s a primitive part of the system. It’s
>> actually not possible to write an encode(to:) method for Optional, since the
>> representation of null values is up to the encoder and the format it’s
>> working in; JSONEncoder, for instance, decides on the representation of nil
>> (JSON null).
>>
> Yes—I noticed that later but then forgot to revise the beginning. Sorry about
> that.
>> It wouldn’t be possible to ask nil to encode itself in a reasonable way.
>>
> I really think it could be done, at least for most coders. I talked about
> this in another email, but in summary:
>
> NSNull would become a primitive type; depending on the format, it would be
> encoded either as a null value or the absence of a value.
> Optional.some(x) would be encoded the same as x.
> Optional.none would be encoded in the following fashion:
> If the Wrapped associated type was itself an optional type, it would be
> encoded as a keyed container containing a single entry. That entry's key
> would be some likely-unique value like "_swiftOptionalDepth"; its value would
> be the number of levels of optionality before reaching a non-optional type.
> If the Wrapped associated type was non-optional, it would be encoded as an
> NSNull.
>
> That sounds complicated, but the runtime already has machinery to coerce
> Optionals to Objective-C id: Optional.some gets bridged as the Wrapped value,
> while Optional.none gets bridged as either NSNull or _SwiftNull, which
> contains a depth. We would simply need to make _SwiftNull conform to Codable,
> and give it a decoding implementation which was clever enough to realize when
> it was being asked to decode a different type.
>> What about a more complex enum, like the standard library's
>> `UnicodeDecodingResult`:
>>
>> enum UnicodeDecodingResult {
>> case emptyInput
>> case error
>> case scalarValue(UnicodeScalar)
>> }
>>
>> Or, say, an `Error`-conforming type from one of my projects:
>>
>> public enum SQLError: Error {
>> case connectionFailed(underlying: Error)
>> case executionFailed(underlying: Error, statement: SQLStatement)
>> case noRecordsFound(statement: SQLStatement)
>> case extraRecordsFound(statement: SQLStatement)
>> case columnInvalid(underlying: Error, key: ColumnSpecifier, statement:
>> SQLStatement)
>> case valueInvalid(underlying: Error, key: AnySQLColumnKey, statement:
>> SQLStatement)
>> }
>>
>> (You can assume that all the types in the associated values are `Codable`.)
>>
>> Sure — these cases specifically do not derive Codable conformance because
>> the specific representation to choose is up to you. Two possible ways to
>> write this, though there are many others (I’m simplifying these cases here a
>> bit, but you can extrapolate this):
>>
> Okay, so tl;dr is "There's nothing special to help with this; just encode
> some indication of the case in one key, and the associated values in separate
> keys". I suppose that works.
>> Have you given any consideration to supporting types which only need to
>> decode? That seems likely to be common when interacting with web services.
>>
>> We have. Ultimately, we decided that the introduction of several protocols
>> to cover encodability, decodability, and both was too much of a cognitive
>> overhead, considering the number of other types we’re also introducing. You
>> can always implement encode(to:) as fatalError().
>>
> I understand that impulse.
>> 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),
>>
>> What does "may" mean here? That, at runtime, the encoder will test for the
>> preferred key type and fall back to the other one? That seems a little bit
>> problematic.
>>
>> Yes, this is the case. A lot is left up to the Encoder because it can choose
>> to do something for its format that your implementation of encode(to:) may
>> not have considered.
>> If you try to encode something with an Int key in a string-keyed dictionary,
>> the encoder may choose to stringify the integer if appropriate for the
>> format. If not, it can reject your key, ignore the call altogether,
>> preconditionFailure(), etc. It is also perfectly legitimate to write an
>> Encoder which supports a flat encoding format — in that case, keys are
>> likely ignored altogether, in which case there is no error to be had. We’d
>> like to not arbitrarily constrain an implementation unless necessary.
>>
> Wait, what? If it's ignoring the keys altogether, how does it know what to
> decode with each call? Do you have to decode in the same order you encoded?
>
> (Or are you saying that the encoder might use the keys to match fields to,
> say, predefined fields in a schema provided to the encoder, but not actually
> write anything about the keys to disk? That would make sense. But ignoring
> the keys altogether doesn't.)
>
> In general, my biggest concern with this design is that, in a hundred
> different places, it is very loosely specified. We have keyed containers, but
> the keys can convert to either, or both, or neither of two different types.
> We have encode and decode halves, but you only have to support one or the
> other. Nils are supported, but they're interpreted as equivalent to the
> absence of a value. If something encounters a problem or incompatibility, it
> should throw an error or trip a precondition.
>
> I worry that this is so loosely specified that you can't really trust an
> arbitrary type to work with an arbitrary encoder; you'll just have to hope
> that your testing touches every variation on every part of the object graph.
>
> This kind of design is commonplace in Objective-C, but Swift developers often
> go to great lengths to expose these kinds of requirements to the type system
> so the compiler can verify them. For instance, I would expect a similar Swift
> framework to explicitly model the raw values of keys as part of the type
> system; if you tried to use a type providing string keys with an encoder that
> required integer keys, the compiler would reject your code. Even when
> something can't be explicitly modeled by the type system, Swift developers
> usually try to document guarantees about how to use APIs safely; for
> instance, Swift.RangeReplaceableCollection explicitly states that its calls
> may make indices retrieved before the call invalid, and individual conforming
> types document specific rules about which indices will keep working and which
> won't.
>
> But your Encoder and Decoder designs seem to document semantics very loosely;
> they don't formally model very important properties, like "Does this coder
> preserve object identities*?" and "What sorts of keys does this coder use?",
> even when it's easy to do so, and now it seems like they also don't specify
> important semantics, like whether or not the encoder is required to inspect
> the key to determine the value you're looking for, either. I'm very concerned
> by that.
>
> The design you propose takes advantage of several Swift niceties—Optional
> value types, enums for keys, etc.—and I really appreciate those things. But
> in its relatively casual attitude towards typing, it still feels like an
> Objective-C design being ported to Swift. I want to encourage you to go
> beyond that.
I agree with this argumentation. This proposal has some great ideas and I can't
wait to be able to use them, but those aspects of the design do feel slightly
at odds with was has become Swift style.
> * That is, if you encode a reference to the same object twice and then decode
> the result, do you get one instance with two references, or two instances
> with one reference each? JSONEncoder can't provide that behavior, but
> NSKeyedArchiver can. There's no way for a type which won't encode properly
> without this property to reject encoders which cannot guarantee it.
>> For these exact reasons, integer keys are not produced by code synthesis,
>> only string keys. If you want integer keys, you’ll have to write them
>> yourself. :)
>>
> That's another thing I realized on a later reading and forgot to correct.
> Sorry about that.
>
> (On the other hand, that reminds me of another minor concern: Your statement
> that superContainer() instances use a key with the integer value 0. I'd
> suggest you document that fact in boldface in the documentation for integer
> keys, because I expect that every developer who uses integer keys will want
> to start at key 0.)
>> So I would suggest the following changes:
>>
>> * The coding key always converts to a string. That means we can eliminate
>> the `CodingKey` protocol and instead use `RawRepresentable where RawValue ==
>> String`, leveraging existing infrastructure. That also means we can call the
>> `CodingKeys` associated type `CodingKey` instead, which is the correct name
>> for it—we're not talking about an `OptionSet` here.
>>
>> * If, to save space on disk, you want to also people to use integers as the
>> serialized representation of a key, we might introduce a parallel
>> `IntegerCodingKey` protocol for that, but every `CodingKey` type should map
>> to `String` first and foremost. Using a protocol here ensures that it can be
>> statically determined at compile time whether a type can be encoded with
>> integer keys, so the compiler can select an overload of
>> `container(keyedBy:)`.
>>
>> * Intrinsically ordered data is encoded as a single value containers of type
>> `Array<Codable>`. (I considered having an `orderedContainer()` method and
>> type, but as I thought about it, I couldn't think of an advantage it would
>> have over `Array`.)
>>
>> This is possible, but I don’t see this as necessarily advantageous over what
>> we currently have. In 99.9% of cases, CodingKey types will have string
>> values anyway — in many cases you won’t have to write the enum yourself to
>> begin with, but even when you do, derived CodingKey conformance will
>> generate string values on your behalf.
>> The only time a key will not have a string value is if the CodingKey
>> protocol is implemented manually and a value is either deliberately left
>> out, or there was a mistake in the implementation; in either case, there
>> wouldn’t have been a valid string value anyway.
>>
> Again, I think this might come down to an Objective-C vs. Swift mindset
> difference. The Objective-C mindset is often "very few people will do X, so
> we might as well allow it". The Swift mindset is more "very few people will
> do X, so we might as well forbid it". :^)
>
> In this case: Very few people will be inconvenienced by a requirement that
> they provide strings in their CodingKeys, so why not require it? Doing so
> ensures that encoders can always rely on a string key being available, and
> with all the magic we're providing to ensure the compiler fills in the actual
> strings for you, users will not find the requirement burdensome.
>> /// 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
>>
>> Speaking of which, I'm not sure about single value containers. My first
>> instinct is to say that methods should be moved from them to the `Encoder`
>> directly, but that would probably cause code duplication. But...isn't there
>> already duplication between the `SingleValue*Container` and the
>> `Keyed*Container`? Why, yes, yes there is. So let's talk about that.
>>
>> In the Alternatives Considered section of the proposal, we detail having
>> done just this. Originally, the requirements now on SingleValueContainer sat
>> on Encoder and Decoder.
>> Unfortunately, this made it too easy to do the wrong thing, and required
>> extra work (in comparison) to do the right thing.
>>
>> When Encoder has encode(_ value: Bool?), encode(_ value: Int?), etc. on it,
>> it’s very intuitive to try to encode values that way:
>>
>> func encode(to encoder: Encoder) throws {
>> // The very first thing I try to type is encoder.enc… and guess what
>> pops up in autocomplete:
>> try encoder.encode(myName)
>> try encoder.encode(myEmail)
>> try encoder.encode(myAddress)
>> }
>> This might look right to someone expecting to be able to encode in an
>> ordered fashion, which is not what these methods do.
>> In addition, for someone expecting keyed encoding methods, this is very
>> confusing. Where are those methods? Where don’t these "default" methods have
>> keys?
>>
>> The very first time that code block ran, it would preconditionFailure() or
>> throw an error, since those methods intend to encode only one single value.
>>
> That's true. But this is mitigated by the fact that the mistake is
> self-correcting—it will definitely cause a precondition to fail the first
> time you make it.
>
> However, I do agree that it's not really a good idea. I'm more interested in
> the second suggestion I had, having the Keyed*Container return a
> SingleValue*Container.
>> The return type of decode(Int.self, forKey: .id) is Int. I’m not convinced
>> that it’s possible to misconstrue that as the correct thing to do here. How
>> would that return a nil value if the value was nil to begin with?
>>
> I think people will generally assume that they're going to get out the value
> they put in, and will be surprised that something encode(_:) accepts will
> cause decode(_:) to error out. I do agree that the type passed to
> `decode(_:forKey_:)` will make it relatively obvious what happened, but I
> think it'd be even better to just preserve the user's types.
>> I think we'd be better off having `encode(_:forKey:)` not take an optional;
>> instead, we should have `Optional` conform to `Codable` and behave in some
>> appropriate way. Exactly how to implement it might be a little tricky
>> because of nested optionals; I suppose a `none` would have to measure how
>> many levels of optionality there are between it and a concrete value, and
>> then encode that information into the data. I think our `NSNull` bridging is
>> doing something broadly similar right now.
>>
>> Optional cannot encode to Codable for the reasons given above. It is a
>> primitive type much like Int and String, and it’s up to the encoder and the
>> format to represent it.
>> How would Optional encode nil?
>>
> I discussed this above: Treat null-ness as a primitive value with its own
> encode() call and do something clever for nested Optionals.
>> It's so simple, it doesn't even need to be specialized. You might even be
>> able to get away with combining the encoding and decoding variants if the
>> subscript comes from a conditional extension. `Value*Container` *does* need
>> to be specialized; it looks like this (modulo the `Optional` issue I
>> mentioned above):
>>
>> Sure, let’s go with this for a moment. Presumably, then, Encoder would be
>> able to vend out both KeyedEncodingContainers and ValueEncodingContainers,
>> correct?
>>
> Yes.
>> public protocol ValueEncodingContainer {
>> func encode<Value : Codable>(_ value: Value?, forKey key: Key) throws
>>
>> I’m assuming that the key here is a typo, correct?
>>
> Yes, sorry. I removed the forKey: label from the other calls, but not this
> one. (I almost left it on all of the calls, which would have been really
> confusing!)
>> Keep in mind that combining these concepts changes the semantics of how
>> single-value encoding works. Right now SingleValueEncodingContainer only
>> allows values of primitive types; this would allow you to encode a value in
>> terms of a different arbitrarily-codable value.
>>
> Yes. I don't really see that as a problem; if you ask `Foo` to encode itself,
> and it only wants to encode a `Bar`, is anything really gained by insisting
> that it add a level of nesting first? More concretely: If you're encoding an
> enum with a `rawValue`, why not just encode the `rawValue`?
>> var codingKeyContext: [CodingKey]
>> }
>>
>> And use sites would look like:
>>
>> func encode(to encoder: Encoder) throws {
>> let container = encoder.container(keyedBy: CodingKey.self)
>> try container[.id].encode(id)
>> try container[.name].encode(name)
>> try container[.birthDate].encode(birthDate)
>> }
>>
>> For consumers, this doesn’t seem to make much of a difference. We’ve turned
>> try container.encode(id, forKey:. id) into try container[.id].encode(id).
>>
> It isn't terribly different for consumers, although the subscript is slightly
> less wordy. But it means that encoders/decoders only provide one—not two—sets
> of encoding/decoding calls, and it allows some small bits of cleverness, like
> passing a SingleValue*Container off to a piece of code that's supposed to
> handle it.
>> These types were chosen because we want the API to make static guarantees
>> about concrete types which all Encoders and Decoders should support. This is
>> somewhat less relevant for JSON, but more relevant for binary formats where
>> the difference between Int16 and Int64 is critical.
>>
>> This turns the concrete type check into a runtime check that Encoder authors
>> need to keep in mind. More so, however, any type can conform to
>> SignedInteger or UnsignedInteger as long as it fulfills the protocol
>> requirements. I can write an Int37 type, but no encoder could make sense of
>> that type, and that failure is a runtime failure. If you want a concrete
>> example, Float80 conforms to FloatingPoint; no popular binary format I’ve
>> seen supports 80-bit floats, though — we cannot prevent that call statically…
>>
>> Instead, we want to offer a static, concrete list of types that Encoders and
>> Decoders must be aware of, and that consumers have guarantees about support
>> for.
>>
> But this way instead forces encoders to treat a whole bunch of types as
> "primitive" which, to those encoders, aren't primitive at all.
>
> Maybe it's just that we have different priorities here, but in general, I
> want an archiving system that (within reason) handles whatever types I throw
> at it, if necessary by augmenting the underlying encoder format with default
> Foundation-provided behavior. If a format only supports 64-bit ints and I
> throw a 128-bit int at it, I don't want it to truncate it or throw up its
> hands; I want it to read the two's-compliment contents of the
> `BinaryInteger.words` property, convert it to a `Data` in some standard
> endianness, and write that out. Or convert to a human-readable `String` and
> use that. It doesn't matter a whole lot, as long as it does something it can
> undo later.
>
> I also like that a system with very few primitives essentially makes no
> assumptions about what a format will need to customize. A low-level binary
> format cares a lot about different integer sizes, but a higher-level one
> probably cares more about dates, URLs, and dictionaries. For instance, I
> suspect (hope?) that the JSONEncoder is going to hook Array and Dictionary to
> make them form JSON arrays and objects, not the sort of key-based
> representation NSKeyedArchiver uses (if I recall correctly). If we just
> provide, for instance, these:
>
> func encode(_ value: String) throws
> func encode(_ value: NSNull) throws
> func encode(_ value: Codable) throws
>
> Then there's exactly one path to customization—test for types in `encode(_:
> Codable)`—and everyone will use it. If you have some gigantic set of
> primitives, many coders will end up being filled with boilerplate to funnel
> ten integer types into one or two implementations, and nobody will be really
> happy with the available set.
>
> In reality, you'll probably need a few more than just these three,
> particularly since BinaryInteger and FloatingPoint both have associated
> values, so several very important features (like their `bitWidth` and
> `isSigned` properties) can only be accessed through a separate primitive. But
> the need for a few doesn't imply that we need a big mess of them,
> particularly when the difference is only relevant to one particular class of
> encoders.
>> To accommodate my previous suggestion of using arrays to represent ordered
>> encoded data, I would add one more primitive:
>>
>> func encode(_ values: [Codable]) throws
>>
>> Collection types are purposefully not primitives here:
>>
>> If Array is a primitive, but does not conform to Codable, then you cannot
>> encode Array<Array<Codable>>.
>> If Array is a primitive, and conforms to Codable, then there may be
>> ambiguity between encode(_ values: [Codable]) and encode(_ value: Codable).
>> Even in cases where there are not, inside of encode(_ values: [Codable]), if
>> I call encode([[1,2],[3,4]]), you’ve lost type information about what’s
>> contained in the array — all you see is Codable
>> If you change it to encode<Value : Codable>(_ values: [Value]) to compensate
>> for that, you still cannot infinitely recurse on what type Value is. Try it
>> with encode([[[[1]]]]) and you’ll see what I mean; at some point the inner
>> types are no longer preserved.
> Hmm, I suppose you're right.
>
> Alternative design: In addition to KeyedContainers, you also have
> OrderedContainers. Like my proposed behavior for KeyedContainers, these
> merely vend SingleValue*Containers—in this case as an Array-like Collection.
>
> extension MyList: Codable {
> func encode(to encoder: Encoder) throws {
> let container = encoder.orderedContainer(self.count)
>
> for (valueContainer, elem) in zip(container, self) {
> try valueContainer.encode(elem)
> }
> }
>
> init(from decoder: Decoder) throws {
> let container = decoder.orderedContainer()
>
> self.init(try container.map { try
> $0.decode(Element.self) })
> }
> }
>
> This helps us draw an important distinction between keyed and ordered
> containers. KeyedContainers locate a value based on the key. Perhaps the way
> in which it's based on the key is that it extracts an integer from the key
> and then finds the matching location in a list of values, but then that's
> just how keys are matched to values in that format. OrderedContainers, on the
> other hand, are contiguous, variable-length, and have an intrinsic order to
> them. If you're handed an OrderedContainer, you are meant to be able to
> enumerate its contents; a KeyedContainer is more opaque than that.
>> (Also, is there any sense in adding `Date` to this set, since it needs
>> special treatment in many of our formats?)
>>
>> We’ve considered adding Date to this list. However, this means that any
>> format that is a part of this system needs to be able to make a decision
>> about how to format dates. Many binary formats have no native
>> representations of dates, so this is not necessarily a guarantee that all
>> formats can make.
>>
>> Looking for additional opinions on this one.
>>
> I think that, if you're taking the view that you want to provide a set of
> pre-specified primitive methods as a list of things you want encoders to make
> a policy decision about, Date is a good candidate. But as I said earlier, I'd
> prefer to radically reduce the set of primitives, not add to it.
>
> IIUC, two of your three proposed, Foundation-provided coders need to do
> something special with dates; perhaps one of the three needs to do something
> special with different integer sizes and types. Think of that as a message
> about your problem domain.
>> I see what you're getting at here, but I don't think this is fit for
>> purpose, because arrays are not simply dictionaries with integer keys—their
>> elements are adjacent and ordered. See my discussion earlier about treating
>> inherently ordered containers as simply single-value `Array`s.
>>
>> You’re right in that arrays are not simply dictionaries with integer keys,
>> but I don’t see where we make that assertion here.
>>
> Well, because you're doing all this with a keyed container. That sort of
> implies that the elements are stored and looked up by key.
>
> Suppose you want to write n elements into a KeyedEncodingContainer. You need
> a different key for each element, but you don't know ahead of time how many
> elements there are. So I guess you'll need to introduce a custom key type for
> no particular reason:
>
> struct /* wat */ IndexCodingKeys: CodingKey {
> var index: Int
>
> init(stringValue: String?, intValue: Int) throws {
> guard let i = intValue ?? Int(stringValue) else {
> throw …
> }
> index = i
> }
>
> var stringValue: String? {
> return String(index)
> }
> var intValue: Int? {
> return index
> }
> }
>
> And then you write them all into keyed slots? And on the way back in, you
> inspect `allKeys` (assuming it's filled in at all, since you keep saying that
> coders don't necessarily have to use the keys), and use that to figure out
> the available elements, and decode them?
>
> I'm just not sure I understand how this is supposed to work reliably when you
> combine arbitrary coders and arbitrary types.
>> The way these containers are handled is completely up to the Encoder. An
>> Encoder producing an array may choose to ignore keys altogether and simply
>> produce an array from the values given to it sequentially. (This is not
>> recommended, but possible.)
>>
> Again, as I said earlier, this idea that a keyed encoder could just ignore
> the keys entirely is very strange and worrying to me. It sounds like a keyed
> container has no dependable semantics at all.
>
> There's preserving implementation flexibility, and then there's being so
> vague about behavior that nothing has any meaning and you can't reliably use
> anything. I'm very worried that, in some places, this design leans towards
> the latter. A keyed container might not write the keys anywhere in the file,
> but it certainly ought to use them to determine which field you're looking
> for. If it doesn't—if the key is just a suggestion—then all this API provides
> is a naming convention for methods that do vaguely similar things,
> potentially in totally incompatible ways.
>> This comes very close to—but doesn't quite—address something else I'm
>> concerned about. What's the preferred way to handle differences in
>> serialization to different formats?
>>
>> Here's what I mean: Suppose I have a BlogPost model, and I can both fetch
>> and post BlogPosts to a cross-platform web service, and store them locally.
>> But when I fetch and post remotely, I ned to conform to the web service's
>> formats; when I store an instance locally, I have a freer hand in designing
>> my storage, and perhaps need to store some extra metadata. How do you
>> imagine handling that sort of situation? Is the answer simply that I should
>> use two different types?
>>
>> This is a valid concern, and one that should likely be addressed.
>>
>> Perhaps the solution is to offer a userInfo : [UserInfoKey : Any]
>> (UserInfoKey being a String-RawRepresentable struct or similar) on Encoder
>> and Decoder set at the top-level to allow passing this type of contextual
>> information from the top level down.
>>
> At a broad level, that's a good idea. But why not provide something more
> precise than a bag of `Any`s here? You're in pure Swift; you have that
> flexibility.
>
> protocol Codable {
> associatedtype CodingContext = ()
>
> init<Coder: Decoder>(from decoder: Coder, with context:
> CodingContext) throws
> func encoder<Coder: Encoder>(from encoder: Coder, with context:
> CodingContext) throws
> }
> protocol Encoder {
> associatedtype CodingContext = ()
>
> func container<Key : CodingKey>(keyedBy type: Key.Type) ->
> KeyedEncodingContainer<Key, CodingContext>
> …
> }
> class KeyedEncodingContainer<Key: CodingKey, CodingContext> {
> func encode<Value: Codable>(_ value: Value,? forKey key: Key,
> with context: Value.CodingContext) throws { … }
>
> // Shorthand when contexts are the same:
> func encode<Value: Codable>(_ value: Value,? forKey key: Key)
> throws
> where Value.CodingContext == CodingContext
> { … }
>
> …
> }
>> We don’t support this type of polymorphic decoding. Because no type
>> information is written into the payload (there’s no safe way to do this that
>> is not currently brittle), there’s no way to tell what’s in there prior to
>> decoding it (and there wouldn’t be a reasonable way to trust what’s in the
>> payload to begin with).
>> We’ve thought through this a lot, but in the end we’re willing to make this
>> tradeoff for security primarily, and simplicity secondarily.
>>
> Well, `String(reflecting: typeInstance)` will give you the fully-qualified
> type name, so you can certainly write it. (If you're worried about
> `debugDescription` on types changing, I'm sure we can provide something,
> either public or as SPI, that won't.) You can't read it and convert it back
> to a type instance, but you can read it and match it against the type
> provided, including by walking into superContainer()s and finding the one
> corresponding to the type instance the user passed. Or you could call a type
> method on the provided type and ask it for a subtype instance to use for
> initialization, forming a sort of class cluster. Or, as a safety measure, you
> can throw if there's a class name mismatch.
>
> (Maybe it'd be better to write out and check the key type, rather than the
> instance type. Hmm.)
>
> Obviously not every encoder will want to write out types—I wouldn't expect
> JSONEncoder to do it, except perhaps with some sort of off-by-default
> option—but I think it could be very useful if added.
>> How important is this performance? If the answer is "eh, not really that
>> much", I could imagine a setup where every "primitive" type eventually
>> represents itself as `String` or `Data`, and each `Encoder`/`Decoder` can
>> use dynamic type checks in `encode(_:)`/`decode(_:)` to define whatever
>> "primitives" it wants for its own format.
>>
>> Does this imply that Int32 should decide how it’s represented as Data? What
>> if an encoder forgets to implement that?
>>
> Yes, Int32 decides how—if the encoder doesn't do anything special to
> represent integers—it should be represented in terms of a more immediately
> serializable type like Data. If an encoder forgets to provide a special
> representation for Int32, then it falls back to a sensible,
> Foundation-provided default. If the encoder author later realizes their
> mistake and wants to correct the encoder, they'd probably better build
> backwards compatibility into the decoder.
>> Again, we want to provide a static list of types that Encoders know they
>> must handle, and thus, consumers have guarantees that those types are
>> supported.
>>
> I do think that consumers are guaranteed these types are supported: Even if
> the encoder doesn't do anything special, Foundation will write them out as
> simpler and simpler types until, sooner or later, you get to something that
> is supported, like Data or String. This is arguably a stronger level of
> guarantee than we have when there are a bunch of primitive types, because if
> an encoder author feels like nobody's going to actually use UInt8 when it's a
> primitive, the natural thing to do is to throw or trap. If the author feels
> the same way about UInt8 when it's not a primitive, then the natural thing to
> do is to let Foundation do what it does, which is write out UInt8 in terms of
> some other type.
>
> --
> Brent Royal-Gordon
> Architechies
>
> _______________________________________________
> 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