Sent from my iPhone
> On Mar 17, 2017, at 3:36 PM, Joe Groff via swift-evolution > <[email protected]> wrote: > > >> On Mar 17, 2017, at 12:49 PM, Itai Ferber <[email protected]> wrote: >> >> On 17 Mar 2017, at 12:18, Michael Gottesman wrote: >> >> >> On Mar 16, 2017, at 10:23 AM, Joe Groff via swift-evolution >> <[email protected]> wrote: >> >> On Mar 16, 2017, at 10:21 AM, Itai Ferber <[email protected]> wrote: >> >> On 15 Mar 2017, at 19:12, Joe Groff wrote: >> >> >> On Mar 15, 2017, at 6:46 PM, Itai Ferber <[email protected]> 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/ 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. Exactly. And given that these types only need to be wrapped by types conforming to Encoder and Decoder which will be relatively rarely implemented I think the tradeoff falls on the side of avoiding a design that requires inheritance. > > -Joe > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
