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

Matthew Johnson matthew at anandabits.com
Sun Mar 19 19:51:06 CDT 2017


> On Mar 19, 2017, at 3:38 PM, Brent Royal-Gordon <brent at architechies.com> wrote:
> 
>> On Mar 19, 2017, at 12:21 PM, Tony Parker <anthony.parker at apple.com <mailto:anthony.parker at apple.com>> wrote:
>>> 
>>> On Mar 19, 2017, at 12:14 PM, Matthew Johnson <matthew at anandabits.com <mailto:matthew at anandabits.com>> wrote:
>>> 
>>> On Mar 19, 2017, at 10:47 AM, Tony Parker <anthony.parker at apple.com <mailto:anthony.parker at apple.com>> wrote:
>>> 
>>>> Hi Matthew, Brent,
>>>> 
>>>> I see why you are asking for this Context parameter, but putting it into the basic Codable protocol introduces too much conceptual overhead. There are too many benefits to keeping adoption to just one protocol, including discoverability, ease of use, reducing the need for overloads on protocols elsewhere, and more. Supporting this one use case does not outweigh those benefits, especially considering I expect that most library code would not use it (as you say: it would be weird to pass this context between modules).
>>>> 
>>>> Can you figure out a way to get the context info passed through the encoder/decoder instead? It would make more sense as something optionally retrieved from the encoder/decoder that was set at the top level.
>>> 
>>> Hi Tony.  I can see the argument that the this is a feature that should be relatively rarely used and thus should have as simple a design as possible.
>>> 
>>> If you feel like the impact of threading a typed context on the API surface area is too heavy you could just add a `var context: Any? { get }` requirement to Encoder and Decoder.  The expectation is that encoders and decoders would accept a context in the top level call and make it available to all Codable types.  This would solve the problem with minimal API impact at the cost of the ability to statically verify that all types receive the context they need to encode / decode correctly.
>>> 
>>> I much prefer the static safety but having a solution is better than not having one.  :)
>> 
>> The Any context property is reasonable, but it would be nice to find something in the middle. =)
>> 
>> One other possibility is that we define a user info dictionary instead, with a custom key type that can be extended (much like our string enumerations). In general I haven’t been a fan of the user info pattern in Swift because of the necessity to cast, but as you say it’s better than nothing. e.g. userInfo : [CodingUserInfoKey: Any].
> 
> I hate casting out of Any, and I strongly believe we should support multiple contexts, so personally, I'd prefer something typed:

I can imagine multiple contexts being useful in rare cases; much more rarely than a single context but still worth supporting.

I generally agree with you about casting.  However, my dislike isn’t the cast itself, but instead it is the lack of a static guarantee.  I’m not sure we’ll find a solution that provides a static guarantee that a required context exists that is also acceptable to the Foundation team.

> 
> 	protocol Encoder {
> 		// Retrieve the context instance of the indicated type.
> 		func context<Context>(ofType type: Context.Type) -> Context?
> 		
> 		// This context is visible for `encode(_:)` calls from this encoder's containers all the way down, recursively.
> 		func addContext<Context>(_ context: Context, ofType type: Context.Type)

What happens if you call `addContext` more than once with values of the same type?  And why do you require the type to be passed explicitly when it is already implied by the type of the value?

> 	}
> 	// Likewise on Decoder
> 	
> 	// Encoder and decoder classes should accept contexts in their top-level API:
> 	open class JSONEncoder {
> 		open func encode<Value : Codable>(_ value: Value, withContexts contexts: [Any] = []) throws -> Data
> 	}

What happens if more than one context of the same type is provided here?  Also, it’s worth pointing out that whatever reason you had for explicitly passing the type above you’re not requiring type information to be provided here.  Whatever design we have it should be self-consistent.

I’m going to speculate that the intent above is that the types of the context values are treated as a key into a dictionary, or something along those lines.  I’ll also speculate that if more than one context of the same type exist are provided at a given stack level level the latest one overwrites the previous one (what else would happen - `addContext` is non-throwing).

Do you think it’s really important to allow users to dynamically provide context for children?  Do you have real world use cases where this is needed?  I’m sure there could be case where this might be useful.  But I also think there is some benefit in knowing that the context used for an entire encoding / decoding is the one you provide at the top level.  I suspect the benefit of a static guarantee that your context is used for the entire encoding / decoding has a lot more value than the ability to dynamically change the context for a subtree.

What benefit do you see in using types as context “keys” rather than something like `CodingUserInfoKey`?  As far as I can tell, it avoids the need for an explicit key which you could argue are somewhat redundant (it would be weird to have two context values of the same type in the cases I know of) and puts the cast in the Encoder / Decoder rather than user code.  These seem like modest, but reasonable wins.  

Unfortunately, I don't think there is a good answer to the question about multiple context values with the same type though.  I can’t think of a good way to prevent this statically.  Worse, the values might not have the same type, but be equally good matches for a type a user requests (i.e. both conform to the same protocol).  I’m not sure how a user-defined encoder / decoder could be expected to find the “best” match using semantics that would make sense to Swift users (i.e. following the rules that are kind of the inverse to overload resolution).  

Even if this were possible there are ambiguous cases where there would be equally good matches.  Which value would a user get when requesting a context in that case?  We definitely don’t want accessing the context to be a trapping or throwing operation.  That leaves returning nil or picking a value at random.  Both are bad choices IMO.

This seems to leave us with the options of having a single context, multiple contexts accessed via an explicit key like Tony has suggested or the earlier design you presented which the Foundation team feels has too much surface area.

The only other direction I can come up with that we might be able to explore is if we could modify the design so it would be possible to build context-aware coders and decoders on top of “basic" encoders / decoders.  I haven’t had a chance to think this through much yet but I suspect changes that would enable this would also be unacceptable to the Foundation team.


> 
> -- 
> Brent Royal-Gordon
> Architechies
> 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170319/5879c7ae/attachment.html>


More information about the swift-evolution mailing list