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

Joe Groff jgroff at apple.com
Wed Mar 15 21:12:46 CDT 2017


> On Mar 15, 2017, at 6:46 PM, Itai Ferber <iferber at apple.com> wrote:
> 
> Thanks Joe, and thanks for passing this along!
> 
> To those who are curious, we use abstract base classes for a cascading list of reasons:
> 
> 	• We need to be able to represent keyed encoding and decoding containers as abstract types which are generic on a key type
> 	• There are two ways to support abstraction in this way: protocol & type constraints, and generic types
> 		• Since Swift protocols are not generic, we unfortunately cannot write protocol KeyedEncodingContainer<Key : CodingKey> { ... }, which is the "ideal" version of what we're trying to represent
> 	• Let's try this with a protocol first (simplified here):
> 
> protocol Container {
>     associatedtype Key : CodingKey
> }
> 
> func container<Key : CodingKey, Cont : Container>(_ type: Key.Type) -> Cont where Cont.Key == Key {
>     // return something
> }
> 
> This looks promising so far — let's try to make it concrete:
> 
> struct ConcreteContainer<K : CodingKey> : Container {
>     typealias Key = K
> }
> 
> func container<Key : CodingKey, Cont : Container>(_ type: Key.Type) -> Cont where Cont.Key == Key {
>     return ConcreteContainer<Key>() // error: Cannot convert return expression of type 'ConcreteContainer<Key>' to return type 'Cont'
> }
> 
> Joe or anyone from the Swift team can describe this better, but this is my poor-man's explanation of why this happens. Swift's type constraints are "directional" in a sense. You can constrain a type going into a function, but not out of a function. There is no type I could return from inside of container() which would satisfy this constraint, because the constraint can only be satisfied by turning Cont into a concrete type from the outside.
> 
> Okay, well let's try this:
> 
> func container... {
>     return ConcreteContainer<Key>() as! Cont
> }
> 
> This compiles fine! Hmm, let's try to use it:
> 
> container(Int.self) // error: Generic parameter 'Cont' could not be inferred
> 
> The type constraint can only be fulfilled from the outside, not the inside. The function call itself has no context for the concrete type that this would return, so this is a no-go.
> 
> 	• If we can't do it with type constraints in this way, is it possible with generic types? Yep! Generic types satisfy this without a problem. However, since we don't have generic protocols, we have to use a generic abstract base class to represent the same concept — an abstract container generic on the type of key which dynamically dispatches to the "real" subclassed type
> 
> Hopes that gives some simplified insight into the nature of this decision.

I see. Protocols with associated types serve the same purpose as generic interfaces in other languages, but we don't have the first-class support for protocol types with associated type constraints (a value of type `Container where Key == K`). That's something we'd like to eventually support. In other places in the standard library, we wrtie the type-erased container by hand, which is why we have `AnySequence`, `AnyCollection`, and `AnyHashable`. You could probably do something similar here; that would be a bit awkward for implementers, but might be easier to migrate forward to where we eventually want to be with the language.

-Joe


More information about the swift-evolution mailing list