> On Mar 17, 2017, at 2:38 PM, Matthew Johnson <[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.
> 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.)
> 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. 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.
* * *
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.
--
Brent Royal-Gordon
Architechies
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution