<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; line-break: after-white-space;" class=""><br class=""><div><blockquote type="cite" class=""><div class="">On Apr 4, 2017, at 6:18 PM, Brent Royal-Gordon &lt;<a href="mailto:brent@architechies.com" class="">brent@architechies.com</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class="">On Apr 4, 2017, at 2:43 PM, Itai Ferber &lt;<a href="mailto:iferber@apple.com" class="">iferber@apple.com</a>&gt; wrote:</div><div class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""><p dir="auto" class="">I like the separation between keyed and unkeyed containers (and I think "unkeyed" is a good name, though not perfect), but I'm not quite happy with the unkeyed container API. Encoding a value into an unkeyed container appends it to the container's end; decoding a value from an unkeyed container removes it from the container's front. These are very important semantics that the method names in question do not imply at all.</p>
</blockquote></div>
<div style="white-space:normal" class=""><p dir="auto" class="">I think that consistency of phrasing is really important here, and the action words "encode" and "decode" are even more important to connote than the semantics of pushing and popping.<br class="">
(Note that there need not be specific directionality to an unkeyed container as long as the ordering of encoded items is eventually maintained on decode.) But on a practical note, names like <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">encodeAtEnd</code> and <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">decodeFromFront</code> (or similar) don't feel like they communicate anything much more useful than the current <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">encode</code>/<code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">decode</code>.</p><div class=""></div></div><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""></blockquote></div></div></div></div></blockquote>Yeah; I stopped short of suggesting specific names because I wasn't totally happy with the ones I could think of. (`appendEncoded` and `decodeNext` were my best ideas, but they don't make a matched pair.)<br class=""></div></div></div></blockquote>Your suggestion in the other thread (which I’m getting to) of <font face="Menlo" class="">encodeNext</font> and <font face="Menlo" class="">decodeNext</font> sound better, but we’ll really need to weigh whether the word "next" adds something significant over not having it at all.<br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""><p dir="auto" class="">Certain aspects of `UnkeyedDecodingContainer` also feel like they do the same things as `Sequence` and `IteratorProtocol`, but in different and incompatible ways. And I certainly think that the `encode(contentsOf:)` methods on `UnkeyedEncodingContainer` could use equivalents on the `UnkeyedDecodingContainer`. Still, the design in this area is much improved compared to the previous iteration.</p>
</blockquote></div>
<div style="white-space:normal" class=""><p dir="auto" class="">Which aspects of <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Sequence</code> and <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">IteratorProtocol</code> do you feel like you're missing on <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">UnkeyedDecodingContainer</code>? Keep in mind that methods on <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">UnkeyedDecodingContainer</code> must be able to throw, and an <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">UnkeyedDecodingContainer</code> can hold heterogeneous items whose type is not known, two things that <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Sequence</code> and <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">IteratorProtocol</code> do not do.</p><div class=""><blockquote type="cite" class=""></blockquote></div></div></div></div></div></blockquote><div class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><div class=""><div class="">Yeah, that's true. One possibility is to use a closure-scoped block which gets passed a sequence that terminates early if it encounters an error:</div></div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>self.pets = try encoder.withDecoded(Pet.self) { seq in</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>return&nbsp;Array(seq)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>// If there is an error, `seq` terminates early, and once we return control from&nbsp;</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>// this closure, `withSequence` will throw it.</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class=""><br class=""></div><div class="">But that's awkward in a couple of different ways.</div></div></div></div></div></div></div></div></blockquote>Agreed<br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><div class=""></div><p dir="auto" class="">In terms of an equivalent to <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">encode(contentsOf:)</code>, keep in mind that this would only work if the collection you're decoding is homogeneous, in which case, you would likely prefer to decode an <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Array</code> over getting an unkeyed container, no? (As soon as conditional conformance arrives in Swift, we will be able to express <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">extension Array : Decodable where Element : Decodable { ... }</code> making decoding homogeneous arrays trivial.)</p><div class=""></div></div><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""></blockquote></div></div></div></div></blockquote>That's true (and I assumed that `Array`s and friends would be `Codable`—we don't even need to wait for conditional conformances ), but it's hardly unheard of to write your own `Collection` types when you need different semantics.</div><div class=""><br class=""></div><div class="">Swift has two mutation protocols that are important for this purpose: `RangeReplaceableCollection` and `SetAlgebra`. You could provide methods on `UnkeyedDecodingContainer` that work with them:</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>func decodeAll&lt;C: RangeReplaceableCollection&gt;(_ type: C.Type) throws -&gt; C where C.Iterator.Element: Encodable {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>var collection = C()</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>if let capacity = self.count {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>collection.reserveCapacity(capacity)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>while !self.isAtEnd {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>collection.append(try self.decode(C.Iterator.Element.self))</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>func decodeAll&lt;S: SetAlgebra&gt;(_ type: S.Type) throws -&gt; S where S.Element: Encodable {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>var set = S()</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>while !self.isAtEnd {<div class=""><span class="Apple-tab-span" style="white-space: pre;">                        </span>set.insert(try self.decode(C.Iterator.Element.self))</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>// Usage:</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>let array = container.decodeAll(ContiguousArray&lt;Pet&gt;.self)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>let set = container.decodeAll(Set&lt;Pet&gt;.self)</div></div></div></div></blockquote>Potentially, thought it would be ambiguous if you had a collection that conformed to both.<br class=""><br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><div class="">Alternatively, you could take a more informal approach, and simply take a function which takes a sequence and returns a value constructed from it. Most collection types have an initializer like that. (Interestingly, this is basically the same as `withDecoded` above, just with a different signature.)</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>// I'm showing this as public, but you could hide it behind an AnyIterator instead.</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>public&nbsp;final class DecodingIterator&lt;Element: Encodable&gt;: IteratorProtocol, Sequence {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>var container: UnkeyedDecodingContainer</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>var decodingError: Error?</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span></div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>init(_ container: UnkeyedDecodingContainer) {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>self.container = container</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span></div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>public&nbsp;func next() {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>if&nbsp;decodingError&nbsp;!= nil || container.isAtEnd {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                                </span>return nil</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>do {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                                </span>return try container.decode(Element.self)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>catch {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                                </span>decodingError = error</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                                </span>return nil</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>public&nbsp;func decodeAll&lt;T: Encodable, Result&gt;(_ makeResult: (DecodingIterator&lt;T&gt;) throws -&gt; Result) throws -&gt; Result {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>let iterator = DecodingSequence&lt;T&gt;(self)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>let result = try makeResult(iterator)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>if let error = iterator.decodingError {</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                        </span>throw error</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>}</div><div class=""><span class="Apple-tab-span" style="white-space:pre">                </span>return result</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>}</div><div class=""><br class=""></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>// Usage:</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>let array = container.decodeAll(ContiguousArray&lt;Pet&gt;.init)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>let set = container.decodeAll(Set&lt;Pet&gt;.init)</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span></div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>// I'm imagining here a future version of Swift where it's possible to&nbsp;</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>// make tuples of `Codable` types `Codable` themselves.</div><div class=""><span class="Apple-tab-span" style="white-space:pre">        </span>let dictionary = container.decodeAll(Dictionary&lt;Pet, Person&gt;.init)</div></div></div></div></blockquote>These are interesting approaches, and I’ll explore them a bit. Just keep in mind that the bar for adding them as public API that we’ll have to maintain is pretty high; with such a large API surface already, these would have to prove significantly beneficial in practice.<br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""><p dir="auto" class="">(Tiny nitpick: I keep finding myself saying "encode into", not "encode to" as the API name suggests. Would that be a better parameter label?)</p>
</blockquote></div>
<div style="white-space:normal" class=""><p dir="auto" class="">On a personal note here — I agree with you, and had originally used "into". However, we've reviewed our APIs and more often have balanced <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">from:/to:</code> rather than <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">from:/into:</code> on read/write/streaming calls. We'd like to rein these in a bit and keep them consistent within our naming guidelines, as much as possible.</p><div class=""></div></div><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""></blockquote></div></div></div></div></blockquote>That's fair. Oh well.<br class=""><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class="">
<div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""><p dir="auto" class="">Should there be a way for an `init(from:)` implementation to determine the type of container in the encoder it's just been handed? Or perhaps the better question is, do we want to promise users that all decoders can tell the difference?</p>
</blockquote></div>
<div style="white-space:normal" class=""><p dir="auto" class="">I think it would be very rare to need this type of information. If a type wants to encode as an array or as a dictionary conditionally, the context for that would likely be present in <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">userInfo</code>.<br class=""></p></div></div></div></blockquote>Yeah, that makes sense. To make that work, though, you might end up having a top-level `Decodable` type read a `version` field or something and write it into the `userInfo` for other types to examine. Nothing necessarily wrong with that, I suppose.</div><div class=""><br class=""></div><div class="">(Hmm...coming back after looking at your `JSONDecoder`, it looks like it might not be possible to do this, or perhaps that it could only be done by storing a reference type into the `userInfo` before you started writing and modifying it during decoding. Is that intentional?)<br class=""></div></div></div></blockquote><font face="Menlo" class="">userInfo</font> dictionaries in general should not be modified during encoding — that feels like a dependency injection attack waiting to happen. (What do you do when another type you don’t control can inspect the&nbsp;<span style="font-family: Menlo;" class="">userInfo</span>&nbsp;dictionary and starts messing with your values, either intentionally or not?)</div><div>If you really must I guess passing a shared reference type in there is possible, but that’s something we’d like to discourage.</div><div><br class=""></div><div>If your type needs this information it should either write it as part of its representation (your type can have its own version field if need be) [most common case], or grab this information if known at the top level before going to decode [applicable in certain instances]. If your type depends on the dynamic state of other types during decoding, you’ll want to sideband that information safely, out of reach of other code.<br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><p dir="auto" class="">
If you really must try to decode regardless, you can always try to grab one container type from the decoder, and if it fails, attempt to grab the other container type.</p><div class=""></div></div><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""></blockquote></div></div></div></blockquote>But I assume that some decoders might just misinterpret whatever is present? For instance, I could imagine an encoder which doesn't natively support keyed containers, so it fakes it by alternating key and value fields in a flat list; if you wrote with a keyed container and read with an unkeyed container, or vice versa, it would then happily misinterpret whatever was present. So it seems like this would probably be a pattern we'd want to discourage.<br class=""></div></div></div></blockquote>If an <font face="Menlo" class="">Encoder</font> does not natively support any one of the features on this API (likely because of the format), it should write data in a way that it can unambiguously understand on the decode side. Whether this means writing a magic identifier or something to the head of the flat list to know to interpret it as a dictionary, or similar, it needs to be able to understand its own data. If this is not possible, then it seems that the <font face="Menlo" class="">Encoder</font>/format is a poor fit for this API.<br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""><p dir="auto" class="">* I think it may make sense to class-constrain some of these protocols. `Encodable` and its containers seem to inherently have reference semantics—otherwise data could never be communicated from all those `encode` calls out to the ultimate caller of the API. Class-constraining would clearly communicate this to both the implementer and the compiler. `Decoder` and its containers don't *inherently* have reference semantics, but I'm not sure it's a good idea to potentially copy around a lot of state in a value type.</p></blockquote></div>
<div style="white-space:normal" class=""><p dir="auto" class="">I don't think class constraints are necessary. You can take a look at the current implementation of <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">JSONEncoder</code> and <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">JSONDecoder</code> <a href="https://github.com/itaiferber/swift/blob/3c59bfa749adad2575975e47130b28b731f763e0/stdlib/public/SDK/Foundation/JSONEncoder.swift" style="color:#3983C4" class="">here</a> (note that this is still a rough implementation and will be updated soon). The model I've followed there is that the encoder itself (<code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">_JSONEncoder</code>) has reference semantics, but the containers (<code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">_JSONKeyedEncodingContainer</code>, <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">_JSONUnkeyedEncodingContainer</code>) are value-type views into the encoder itself.</p><p dir="auto" class="">Keep in mind that during the encoding process, the entities created most often will be containers. Without some additional optimizations in place, you end up with a <em class="">lot</em> of small, short-lived class allocations as containers are brought into and out of scope.<br class="">
By not requiring the class constraints, it's at least possible to make all those containers value types with references to the shared encoder.</p><div class=""></div></div><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""></blockquote></div></div></div></blockquote>Okay, I see what you're getting at. But that still makes me think that:</div><div class=""><br class=""></div><div class="">* `Encoder` and `Decoder` should be class-constrained. An `Encoder` *must* share some kind of reference with its containers, and while it would certainly be possible to hide that reference away somewhere inside a value-typed `Encoder`, I can't imagine it's worth penalties like having to pass a larger existential container (`class`-constrained protocol existentials are 2+ words instead of 5+) and having to virtualize reference-counting operations through a value witness table. (See <a href="https://github.com/apple/swift/blob/master/docs/ABI.rst#class-existential-containers" class="">https://github.com/apple/swift/blob/master/docs/ABI.rst#class-existential-containers</a> for details here.)</div></div></div></blockquote><div>The <font face="Menlo" class="">Encoder</font> and <font face="Menlo" class="">Decoder</font> need not be class constrained, for the same reasons. Somewhere down the line, something must have reference semantics, you’re right, but the thing which is shared need not be the <font face="Menlo" class="">Encoder</font> either.</div><div><br class=""></div><div>For instance, consider one possible design of a streaming encoder. The <font face="Menlo" class="">Encoder</font> and its containers may all be structs with an offset into a data stream or blob; writing to a container simply appends to the stream. It’s not necessarily the <font face="Menlo" class="">Encoder</font> that needs to be shared, but potentially the underlying data store. We wouldn’t want to enforce a design choice that would prevent this kind of setup if it benefitted the encoder and its format.</div><br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">* There is no need for the `encode` calls to be marked `mutating`. An encoding container is just a view on state within a reference type, so it doesn't mutate the container itself, only the portion of the encoder the container refers to. (It is not possible for an encoding container to hold value-typed state, either, because the encoder never gets a chance to grab that state once you're finished with it.) While these calls do mutate *some* state *somewhere*, they don't mutate the state of the container.</div></div></div></blockquote><div>Again, not necessarily true. The container may want to maintain state on its own that is not shared with the <font face="Menlo" class="">Encoder</font>. You can imagine a container which queues up all of the encode calls it receives and builds up a mini payload, and only produces data once the <font face="Menlo" class="">Encoder</font> finalizes the container, or similar. It would have state of its own to modify without modifying the <font face="Menlo" class="">Encoder</font> directly.</div><div><br class=""></div><div>You’re right in the general case, and in many instances, the mutating is unnecessary. But what we’re going for here is not arbitrarily preventing design decisions that might make sense in the context of a specific format. If the methods are not marked as mutating, then individual containers could not store their own state without delegating to a separate reference type.</div><br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">Basically, what I'm saying is that `Encoder` should always be a reference *type* and encoding containers, even if implemented with a value *type*, should always have reference *semantics*.</div><div class=""><br class=""></div><div class="">(The `decode` calls on an `UnkeyedDecodingContainer` are a harder case, because it really *is* plausible that the "next element" pointer might be stored in the container rather than the decoder. I'm not totally sure what to do there.)</div></div></div></blockquote>Yes, this is the type of local state that’s needed. <font face="Menlo" class="">UnkeyedDecodingContainer</font>, for instance, may have an index into the array that it holds. Every decode call will need to increment that index by one. Less likely on encode, but still possible.</div><div><br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">(Oh, one other thing: Does `UnkeyedDecodingContainer.count` return the total number of elements, if known, or the number of elements *remaining*, if known? The latter sounds more useful to me.)<br class=""></div></div></div></blockquote>Currently returns the total number of elements in the container, if known. One can be computed from the other, either way.</div><div>The total count is the number remaining before you’ve decoded any elements; the number remaining is the total count minus the number of elements you’ve already decoded.<br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class="">
<div style="white-space:normal" class=""><p dir="auto" class="">Having implemented these myself multiple times, I agree — it can be a pain to repeat these implementations, and if you look at the linked implementations above, funneling to one method from all of those is exactly what I do (and in fact, this can be shortened significantly, which I plan on doing soon).</p><p dir="auto" class="">There is a tradeoff here between ease of use for the end consumer of the API, and ease of coding for the writer of a new <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Encoder</code>/<code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Decoder</code>, and my argument will always be for the benefit of the end consumer. (There will be orders of magnitude more end consumers of this API than those writing new <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Encoder</code>s and <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Decoder</code>s 😉)<br class=""></p></div></div></div></blockquote>This is true—we must put the API's consumer first.<br class=""><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><p dir="auto" class="">
Think of the experience for the consumer of this API, especially someone learning it for the first time. It can already be somewhat of a hurdle to figure out what kind of container you need, but even once you get a keyed container (which is what we want to encourage), then what? You start typing <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">container.enc...</code> and in the autocomplete list in Xcode, the only thing that shows up is one autocomplete result: <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">encode(value: Encodable, forKey: ...)</code> Consider the clarity (or lack thereof) of that, as opposed to seeing <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">encode(value: Int, forKey: ...)</code>, <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">encode(value: String, forKey: ...)</code>, etc. Given a list of types that users are already familiar with helps immensely with pushing them in the right direction and reducing cognitive load. When you see <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">String</code> in that list, you don't have to question whether it's possible to encode a string or not, you just pick it. I have an <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Int8</code>, can I encode it? Ah, it's in the list, so I can.</p><div class=""><blockquote type="cite" class=""></blockquote></div></div></div></div></blockquote><div class=""><div class=""><div class=""><div style="font-family: sans-serif; white-space: normal;" class=""><div class="">This is a very interesting argument that I hadn't considered. But I think it still fails, because the list of primitive types doesn't convey what you want developers to understand about the types they can encode.</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class="">Imagine being new to the API. You start writing your first `encode(to:)` method, and you get to this point:</div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>func encode(to encoder: Encoder) throws {</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">                </span>encoder.</div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class="">You expect to see some way to start putting types in the encoder, but instead you get this list:</div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>[CodingKey?]<span class="Apple-tab-span" style="white-space:pre">                                        </span>codingPath</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>KeyedEncodingContainer&lt;Key&gt;<span class="Apple-tab-span" style="white-space:pre">        </span>container(keyedBy: CodingKey.Protocol)</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>SingleValueEncodingContainer<span class="Apple-tab-span" style="white-space: pre;">        </span>singleValueContainer()</div><div class=""><span class="Apple-tab-span" style="font-family: sans-serif; white-space: pre;">        </span><font face="sans-serif" class="">[CodingUserInfoKey&nbsp;:&nbsp;Any]<span class="Apple-tab-span" style="white-space:pre">                </span>userInfo</font></div><div class=""><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>UnkeyedEncodingContainer<span class="Apple-tab-span" style="white-space: pre;">                </span>unkeyedContainer()</div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class="">Okay, two of these are obviously just properties, and the other three ask you to choose a "container". Maybe you read some documentation or tutorials, maybe you just guess, but either way, you figure out that you want a keyed container and that you need a CodingKey enum to key it with.</div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class="">So you make a keyed container, assign it to a variable, and get to this point:</div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>func encode(to encoder: Encoder) throws {</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">                </span>var container = encoder.container(keyedBy: CodingKeys.self)</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">                </span></div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">                </span>container.</div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class="">When you ask autocomplete what you can do with your shiny new container, you get this:</div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>[CodingKey?]<span class="Apple-tab-span" style="white-space: pre;">                                                </span>codingPath</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Bool?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Data?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Double?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Encodable?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Float?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Int16?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Int32?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Int64?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Int8?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Int?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: String?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: UInt16?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: UInt32?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: UInt64?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: UInt8?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: UInt?, forKey: MyModel.CodingKeys) throws</div></div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>Void<span class="Apple-tab-span" style="white-space:pre">                                                                </span>encodeWeak(object: (AnyObject &amp;…), forKey: MyModel.CodingKeys) throws</div><div class=""><span class="Apple-tab-span" style="font-family: sans-serif; white-space: pre;">        </span><font face="sans-serif" class="">KeyedEncodingContainer&lt;CodingKey&gt;<span class="Apple-tab-span" style="white-space:pre">        </span>nestedContainer(keyedBy: CodingKey.Protocol, forKey: MyModel.CodingKeys)</font></div><div class=""><font face="sans-serif" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>UnkeyedEncodingContainer<span class="Apple-tab-span" style="white-space:pre">                        </span>nestedUnkeyedContainer(forKey: MyModel.CodingKeys)</font></div><div class=""><font face="sans-serif" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>Encoder<span class="Apple-tab-span" style="white-space:pre">                                                        </span>superEncoder()</font></div><div class=""><font face="sans-serif" class=""><span class="Apple-tab-span" style="white-space:pre">        </span>Encoder<span class="Apple-tab-span" style="white-space:pre">                                                        </span>superEncoder(forKey:&nbsp;</font><span style="font-family: sans-serif;" class="">MyModel.CodingKeys)</span></div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div style="font-family: sans-serif; white-space: normal;" class="">I think this list would give a new developer an *entirely* incorrect impression of how you're supposed to encode your type. The list contains *ten* integer types, *two* floating-point types, `Bool`, `Data`, `String`, and, oh yeah, the overload that handles literally everything else. The impression I would get is that this supports only the listed types—why else would we list every variety of integer, for instance?—and the types *users* conform to `Encodable`, if I noticed that overload at all. Maybe the nested containers are for coding nested arrays and dictionaries—there's certainly no reason this list would make me believe I can encode those directly.</div></div></div></div></div></div></div></div></blockquote>I agree with most of your points; I anticipated to get this response, and for the most part, you’re right.</div><div>This is a large API, and you’d have to drill down through the <font face="Menlo" class="">container(keyedBy:)</font> method before you’d even get to the list.</div><div><br class=""></div><div>But: to be honest, I expect many developers will learn about this API either through our documentation, or through online tutorials, StackOverflow, etc. I think those resources over the years have been good enough at pushing new developers toward the right way to do things; for instance, I rarely if ever see anyone accidentally using <font face="Menlo" class="">NSArchiver</font> instead of <font face="Menlo" class="">NSKeyedUnarchiver</font> for encoding objects. Documentation and the community have been good about teaching the "right" way to do things, and for the most part, I don’t think many of our existing API customers would accidentally go for an encoding model without keys, even if just out of habit. For new developers, I expect documentation and community resources to push them in the right direction.</div><div><br class=""></div><div>The point I want to get at though is that "keyed vs. unkeyed" can be somewhat more of a mindset, and once you learn that keyed is almost always what you want, that’s something you reach for instinctively, and consistently. Learning to use the keyed container is something you learn once, and that’s what you’ll keep reaching for. What you can <i class="">do</i> with a keyed container, though, is something you will want to review on a case-by-case basis. Having the list of overloads in place helps reinforce that some of these types are special over others, and being able to review that list concretely is nice, even if just for reference.</div><div><br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><div class=""><div class=""><div class=""><div style="font-family: sans-serif; white-space: normal;" class="">If we wanted the overload list to help beginners understand which types `encode(_:forKey:)` supports, I would want it to look more like:</div><div style="font-family: sans-serif; white-space: normal;" class=""><br class=""></div><div class=""><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>[CodingKey?]<span class="Apple-tab-span" style="white-space: pre;">                                                </span>codingPath</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: [Encodable]?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: [Encodable &amp; Hashable: Encodable]?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Data?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Date?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Double?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Encodable?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Int?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Set&lt;Encodable &amp; Hashable&gt;?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: String?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: URL?, forKey: MyModel.CodingKeys) throws</div><div style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encodeWeak(object: (AnyObject &amp;…), forKey: MyModel.CodingKeys) throws</div><div style="font-family: Helvetica; white-space: normal;" class=""><span class="Apple-tab-span" style="font-family: sans-serif; white-space: pre;">        </span><font face="sans-serif" class="">KeyedEncodingContainer&lt;CodingKey&gt;<span class="Apple-tab-span" style="white-space: pre;">        </span>nestedContainer(keyedBy: CodingKey.Protocol, forKey: MyModel.CodingKeys)</font></div><div style="font-family: Helvetica; white-space: normal;" class=""><font face="sans-serif" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>UnkeyedEncodingContainer<span class="Apple-tab-span" style="white-space: pre;">                        </span>nestedUnkeyedContainer(forKey: MyModel.CodingKeys)</font></div><div style="font-family: Helvetica; white-space: normal;" class=""><font face="sans-serif" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Encoder<span class="Apple-tab-span" style="white-space: pre;">                                                        </span>superEncoder()</font></div><div style="font-family: Helvetica; white-space: normal;" class=""><font face="sans-serif" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Encoder<span class="Apple-tab-span" style="white-space: pre;">                                                        </span>superEncoder(forKey:&nbsp;</font><span style="font-family: sans-serif;" class="">MyModel.CodingKeys)</span></div><div style="font-family: sans-serif; white-space: normal;" class=""><span style="font-family: sans-serif;" class=""><br class=""></span></div><div style="font-family: sans-serif; white-space: normal;" class=""><span style="font-family: sans-serif;" class="">These ten overloads more fairly represent the kinds of Swift- and Foundation-provided encodable types people actually use most often. The presence of `Array`, `Dictionary`, and `Set` convey that encoded types may have a complex inner structure. The absence of all the `Int`/`UInt` varieties does mean it's not immediately obvious that it supports every single one, but Swift guidelines highly discourage use of those types anyway unless you *really* need those semantics for some reason.</span></div></div></div></div></div></div></div></div></blockquote><div>Sure, I don’t disagree that this list would likely be most helpful for beginners. But on the other hand, it doesn’t correctly reflect the actual interface on an encoding container, which is more important in general.</div><br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><div class=""><div class=""><div class=""><div class=""><div style="font-family: sans-serif; white-space: normal;" class=""><span style="font-family: sans-serif;" class="">Or, alternatively, just offer the one overload:</span></div><div style="font-family: sans-serif; white-space: normal;" class=""><span style="font-family: sans-serif;" class=""><br class=""></span></div><div style="font-family: sans-serif; white-space: normal;" class=""><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>[CodingKey?]<span class="Apple-tab-span" style="white-space: pre;">                                                </span>codingPath</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encode(value: Encodable?, forKey: MyModel.CodingKeys) throws</div><div class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Void<span class="Apple-tab-span" style="white-space: pre;">                                                                </span>encodeWeak(object: (AnyObject &amp;…), forKey: MyModel.CodingKeys) throws</div><div style="font-family: Helvetica;" class=""><span class="Apple-tab-span" style="font-family: sans-serif; white-space: pre;">        </span><font face="sans-serif" class="">KeyedEncodingContainer&lt;CodingKey&gt;<span class="Apple-tab-span" style="white-space: pre;">        </span>nestedContainer(keyedBy: CodingKey.Protocol, forKey: MyModel.CodingKeys)</font></div><div style="font-family: Helvetica;" class=""><font face="sans-serif" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>UnkeyedEncodingContainer<span class="Apple-tab-span" style="white-space: pre;">                        </span>nestedUnkeyedContainer(forKey: MyModel.CodingKeys)</font></div><div style="font-family: Helvetica;" class=""><font face="sans-serif" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Encoder<span class="Apple-tab-span" style="white-space: pre;">                                                        </span>superEncoder()</font></div><div style="font-family: Helvetica;" class=""><font face="sans-serif" class=""><span class="Apple-tab-span" style="white-space: pre;">        </span>Encoder<span class="Apple-tab-span" style="white-space: pre;">                                                        </span>superEncoder(forKey:&nbsp;</font><span style="font-family: sans-serif;" class="">MyModel.CodingKeys)</span></div></div><div style="font-family: sans-serif; white-space: normal;" class=""><span style="font-family: sans-serif;" class=""><br class=""></span></div><div style="font-family: sans-serif; white-space: normal;" class=""><span style="font-family: sans-serif;" class="">And make sure the little two-sentence blurb at the bottom of the autocomplete list mentions some of the compatible types:</span></div><div style="font-family: sans-serif; white-space: normal;" class=""><span style="font-family: sans-serif;" class=""><br class=""></span></div><div class=""><span style="font-family: sans-serif; white-space: normal;" class=""><span class="Apple-tab-span" style="white-space:pre">        </span></span><font face="sans-serif" class="">Encodes the given value for the given key. Built-in Encodable types include most basic value types in the standard library and Foundation, such as Int, Double, String, Date, and URL, plus Array, Dictionary, and Set when their elements are Encodable.</font></div></div></div></div></div><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><p dir="auto" class="">Even for advanced users of the API, though, there's something to be said for static guarantees in the overloading. As someone familiar with the API (who might even know all the primitives by heart), I might wonder if the <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Encoder</code> I'm using has correctly switched on the generic type. (It would have to be a dynamic switch.) Did the implementer remember to switch on <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">Int16</code> correctly? Or did they forget it and will I be falling into a generic case which is not appropriate here?</p></div></div></div></blockquote><font color="#5856d6" face="sans-serif" class="">I get that, which is why I'm *not* suggesting going to `switch`; I'm suggesting that primitives all be handled thorough single-value containers. An advanced user would know that all of the single-value container's types were explicitly implemented by every encoder.<br class=""></font></div></div></div></blockquote>But if you implement&nbsp;<font face="Menlo" class="">encode&lt;T : Encodable&gt;(_ value: T, forKey key: Key)</font>&nbsp;by fetching a single value container and writing to it, you’d still have to check if <font face="Menlo" class="">T</font> is <font face="Menlo" class="">Bool</font> or <font face="Menlo" class="">Int</font> or <font face="Menlo" class="">Int8</font> or <font face="Menlo" class="">Int16</font> or… etc. in order to be able to call the right thing on the container.</div><div><br class=""></div><div>Unless you mean that these types are not handled specially in the first place, and you let them go through the regular generic path so that they request a single value container on their own… This is possible, but you’d be missing out on an enormous optimization. Every single primitive encode would go through <font face="Menlo" class="">container.encode(…, forKey:…)</font>&nbsp;→ create a new <font face="Menlo" class="">Encoder</font> reference → <font face="Menlo" class="">PrimitiveType.encode(to:)</font>&nbsp;→ request a single value container from the <font face="Menlo" class="">Encoder</font>&nbsp;→ <font face="Menlo" class="">container.encode(…)</font>, all instead of <font face="Menlo" class="">container.encode(…, forKey:…)</font>&nbsp;→ store primitive value directly.</div><div><br class=""></div><div>Even in the most optimized of cases (where the container already references an encoder, and the encoder conforms to <font face="Menlo" class="">SingleValueContainer</font> directly such that it can just <font face="Menlo" class="">return self</font>), you’re paying for 3 extra method calls (at least one dispatching through an existential) on every single primitive encode.</div><div><br class=""></div><div>So in the worst case, if you don’t switch on the type, it will just be slow. If the default implementation is not appropriate for a given type, though, nothing will be in place to catch it if you forget to implement that...<br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class=""><div class="" style="font-family: sans-serif;"><div class=""></div></div></div></blockquote><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><p dir="auto" class="">Let's take a step back, though. This is mostly annoying to implement because of the repetition, right? If someone were to put together a proposal for a proper macro system in Swift, which is <em class="">really</em> what we want here, I wouldn't be sad. 😉</p></div></div></div></blockquote><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><div class=""></div></div><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""></blockquote></div></div></div></blockquote>Don't tempt me!<br class=""><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class="">
<div style="white-space:normal" class=""><p dir="auto" class="">This is already unfortunately a no-go. As mentioned in other emails, you cannot override an <code style="background-color:#F7F7F7; border-radius:3px; margin:0; padding:0 0.4em" bgcolor="#F7F7F7" class="">associatetype</code> in a subclass of a class, which means that you cannot require a different container type than your superclass. This is especially problematic in the default case where we'd want to encourage types to use keyed containers — every type should have its own keys, and you'd need to have a different keyed container than your parent, keyed on your keys.</p></div></div></div></blockquote><div class="">Ugh, that's too bad. Class inheritance ruins everything, as usual.</div></div></div></div></blockquote>And yet, a very important use case for this API. :) (I don’t think we’re in disagreement here, just saying.)</div><div><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class=""><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class=""><div style="white-space:normal" class=""><p dir="auto" class="">This might address my last point above, but then what useful interface would <code bgcolor="#F7F7F7" class="" style="background-color: rgb(247, 247, 247); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; margin: 0px; padding: 0px 0.4em;">EncodingSink</code> and <code bgcolor="#F7F7F7" class="" style="background-color: rgb(247, 247, 247); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; margin: 0px; padding: 0px 0.4em;">DecodingSource</code> have if a type conforming to <code bgcolor="#F7F7F7" class="" style="background-color: rgb(247, 247, 247); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; margin: 0px; padding: 0px 0.4em;">EncodingSink</code> could be any one of the containers or even a whole encoder itself?</p></div><div style="white-space:normal" class=""><div class=""></div></div><div style="white-space:normal" class=""><blockquote style="border-left:2px solid #777; color:#777; margin:0 0 5px; padding-left:5px" class=""></blockquote></div></div></div></blockquote>`EncodingSink`'s members are used by the encoder to convert itself into whatever type a given `Encodable` type's `encode(to:)` method wants to write itself into. The `encode(to:)` method itself knows which concrete type it wants to use, and uses the interface of that concrete type to do its work.</div><div class=""><br class=""></div><div class="">(But as you said, the associatedtype issue prevents this approach.)<br class=""><blockquote type="cite" class=""><div class=""><div style="font-family:sans-serif" class="">
<div style="white-space:normal" class=""><p dir="auto" class="">I am curious, though, about your comment above on preconditions being unenforceable, because this is certainly something we would want to hammer out before releasing. What cases are you thinking of that are unenforceable?</p></div><div style="white-space:normal" class="">
</div>
</div>
</div>

</blockquote>I'm just imagining somebody thinking they can construct several different containers from the same encoder—for instance, making a keyed and an unkeyed container that are peers to one another, or making two keyed containers with different coding key types (with the encoder preventing collisions by including the coding key type alongside the stringValue). When I was first reading the proposal, I was under the impression that coding key types would prevent collisions between, for instance, superclass and subclass keys; I realized a little later in my read-through that you weren't taking that approach, but I could see someone else making the same mistake and not realizing it until they'd already written a lot of code.</div><div class=""><br class=""></div><div class="">* * *</div><div class=""><br class=""></div><div class="">Apropos of none of this discussion so far, one other thing I just noticed: `JSONEncoder` and `JSONDecoder` have a lot of parallel "strategy" properties, but they use different types for them. Have you considered making a single `JSONCodingStrategy` type which expresses bidirectional rules? That would allow a developer to make a constant containing their strategy and use it as a single source of truth.</div></div></div></blockquote>This has been brought up in past review. It might seem alluring to combine these, but in practice, two out of the four strategies cannot be reasonably combined because their types differ (<font face="Menlo" class="">Dat{e,a}{En,De}codingStrategy</font> — their custom closures have differing types). It’s possible to create <font face="Menlo" class="">.custom</font> cases which take backward and forward closures, but then what if you only care about one direction? If the closures are optional, then it’s possible to give a <font face="Menlo" class="">.custom</font> case with two nil closures, or even just a nil closure for the wrong direction.</div><div><br class=""></div><div>Overall, just a bit too much complexity to be worth it.</div><div><br class=""><blockquote type="cite" class=""><div class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><div class="">
<span class="Apple-style-span" style="border-collapse: separate; font-variant-ligatures: normal; font-variant-east-asian: normal; font-variant-position: normal; line-height: normal; border-spacing: 0px;"><div class=""><div style="font-size: 12px; " class="">--&nbsp;</div><div style="font-size: 12px; " class="">Brent Royal-Gordon</div><div style="font-size: 12px; " class="">Architechies</div></div></span></div></div></div></blockquote>Thanks again for your comments! Continuing to respond to other emails in the chain.</div></body></html>