> On Mar 17, 2017, at 5:13 PM, Brent Royal-Gordon <[email protected]> 
> wrote:
> 
>> On Mar 17, 2017, at 2:38 PM, Matthew Johnson <[email protected] 
>> <mailto:[email protected]>> wrote:
>> 
>>> 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
>>>             { … }
>>>             
>>>             …
>>>     }
>> 
>> This is sort of similar to the design I suggested for contexts.  The 
>> difference is that you’re requiring all Codable to be context aware and by 
>> introducing an associated type you break the ability to use Codable as an 
>> existential.
> 
> I don't think banning existentials is actually a loss. Since `encode(_:)` 
> doesn't record type information, and instead `decode(_:)` requires the exact 
> concrete type to be passed in, `Codable` existentials cannot be usefully 
> encoded or decoded. For instance, a heterogeneous `[Codable]` would encode in 
> several different, probably mutually incompatible formats, without any type 
> information that could distinguish between them. Since the only semantics of 
> `Codable` are encoding and decoding, and decoding is always done by an 
> `init`, `Codable` existentials are useless and we lose nothing by not 
> supporting them.

That’s fair.  But how would you change the design of the NSKeyedArchiver / 
NSKeyedUnarchiver extensions which use the existentials? 

> 
>> Many Codable conforming types won’t need to know anything about a context.  
>> I would still want to be able to encode them along with my custom 
>> context-aware types.  A good example is types from Foundation that will 
>> conform to Codable.  They will definitely not know anything about my context 
>> but I still want to be able to encode a URL alongside my custom 
>> context-aware types.
> 
> Sure; you can do that by calling `encode(_:forKey:with:)` and passing a 
> freshly-made `()` context. We might even add a second convenience overload of 
> `encode(_:forKey:)`:
> 
>       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
>               {
>                       try encode(value, forKey: key, with: currentContext)
>               }
>               
>               // Shorthand when the type uses a Void context:
>               func encode<Value: Codable>(_ value: Value,? forKey key: Key) 
> throws
>                       where Value.CodingContext == Void
>               {
>                       try encode(value, forKey: key, with: ())
>               }
>               
>               …
>       }
> 
> The main disadvantage I can think of in this design is that even `Codable` 
> users who don't need a context have to have a `with context: Void` in their 
> code. This might be confusing to new developers, but I think it's worth it.
> 
> (I don't think I mentioned this anywhere, but containers like `Array` should 
> take on the `CodingContext` of their `Element`s and pass the context they 
> receive through without examining it. That would probably be pretty common 
> with generic container types.)

You’re right - I just wasn’t thinking about this clearly.  I missed that you 
were requiring Codable types to manually thread the context through.  This is 
kind of unfortunate when *all* types involved in the encoding either have a 
Void context or use the same context type.  On the other hand, it is a somewhat 
rarely needed feature and this approach offers a lot of flexibility.  I think I 
like it.

> 
>> Did you take a look at the design I suggested?  What do you think of it?
> 
> I think that, if a type wants to support context-free coding, it should use 
> an optional `CodingContext`. :^)
> 
> In all seriousness, I see the design as very slightly weak, in that it makes 
> it easy to forget to pass a context through, but quite acceptable.

Easy for who?  I was not requiring Codable types to thread it through at all.  
The context was fully managed by the Encoder / Decoder type.  The only place 
Codable types work with the context is as an argument they receive.  They never 
pass it when encoding or decoding anything.  The Encoder / Decoder would need 
to store the context internally and when call is made to encode / decode a 
ContextAwareCodable it would pass the result of a dynamic cast to 
ContextAwareCodable.Context as the context.

This design encapsulates the context more completely and solves all the real 
world use cases I know of at the expense of some flexibility.  It also 
guarantees that *all* Codables in a single encoding / decoding see exactly the 
same context or no context at all.  This could be viewed as an advantage or a 
disadvantage.

Maybe your approach of making the context more exposed but also offering more 
flexibility and guaranteeing a Codable always gets the context it needs is 
better.  I need more time to think about it, but I think it makes better 
tradeoffs.

> It would certainly solve the `with context: Void` problem I mentioned. I 
> might consider reversing the relationship between the two protocols, though:
> 
>       public protocol ContextAwareCodable {
>               associatedtype CodingContext
>               
>               init(from decoder: Decoder, with context: CodingContext) throws
>               func encode(to encoder: Encoder, with context: CodingContext) 
> throws
>       }
>       public protocol Codable: ContextAwareCodable where CodingContext == 
> Void {
>               init(from decoder: Decoder) throws
>               func encode(to encoder: Encoder) throws
>       }
>       extension Codable {
>               public init(from decoder: Decoder, with context: Void) throws {
>                       try self.init(from: decoder)
>               }
>               func encode(to encoder: Encoder, with context: Void) throws {
>                       try encode(to: encoder)
>               }
>       }
> 
> Most `Encoder`/`Decoder` APIs would have to use `ContextAwareCodable`, but if 
> you're writing a coder, you'd better be aware of contexts.

The reason I did it the other way is to allow Codable to be used as an 
existential.  It is used that way in the NSKeyedArchiver / NSKeyedUnarchiver 
extensions and I wanted to find something workable that wouldn’t break that.  
If we aren’t worried about existentials then this would work.

Agree - the design priority should be for users and authors of Codable types.  
Encoders and Decoders are comparatively rare and written by people who should 
know what they are doing.

> 
> * * *
> 
> A thought I just had: Someone upthread mentioned that `Codable` might be 
> better as part of the standard library. One reason to favor that approach is 
> that you could then make `Codable` support a requirement of types like 
> `BinaryInteger` and `FloatingPoint`.
> 
> It might still make sense to have the coders themselves be part of 
> Foundation; only the protocols defining `Codable`, `Encoder`, `Decoder`, and 
> their ancillary types would be part of the standard library.

+1 to putting the protocols in the standard library and keeping the concrete 
encoders and decoders in Foundation.

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

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to