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

Joe Groff jgroff at apple.com
Fri Mar 17 15:36:04 CDT 2017


> On Mar 17, 2017, at 12:49 PM, Itai Ferber <iferber at apple.com> wrote:
> 
> On 17 Mar 2017, at 12:18, Michael Gottesman wrote:
> 
> 
> On Mar 16, 2017, at 10:23 AM, Joe Groff via swift-evolution <swift-evolution at swift.org> wrote:
> 
> On Mar 16, 2017, at 10:21 AM, Itai Ferber <iferber at apple.com> wrote:
> 
> On 15 Mar 2017, at 19:12, Joe Groff wrote:
> 
> 
> 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
> 
> Yep, that’s a good way to describe it.
> We could potentially do that as well, but adding another type like AnyHashable or AnyCollection felt like a much more sweeping change, considering that those require some special compiler magic themselves (and we’d like to do as little of that as we can).
> 
> AnyCollection doesn't have any special compiler magic. AnyHashable's only magic is that it has implicit conversions, but that would become normal behavior once it can be replaced by a plain Hashable existential type.
> 
> Hey Itai. I am not sure if I missed this. But did you follow up with why you didn't want to use AnyCollection/AnyHashable? The thread got really long pretty fast.
> 
> I responded to this in a different part of the thread very recently. Can you elaborate on how a type like AnyCollection/AnyHashable would help here? More important than the type erasure is the type being generic on the key type, and this must be specified. How would this be possible
> 
> 

You can implement an AnyContainer<Key> type that conforms to the Container protocol, using an abstract base class that erases the type, and a private subclass that forwards the interface to a contained value. https://www.bignerdranch.com/blog/breaking-down-type-erasure-in-swift/ <https://www.bignerdranch.com/blog/breaking-down-type-erasure-in-swift/> runs the technique down fairly well. I don't see any obvious reason we couldn't do that here. The tradeoff I see is whether the inconvenience of manually wrapping conforming types in AnyContainer outweighs
the constraint that container implementations must be subclasses instead of having the full freedom of being value types or subclasses in a different class hierarchy conforming to a protocol.

-Joe
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170317/ef7ec1c1/attachment-0001.html>


More information about the swift-evolution mailing list