[swift-evolution] Make generics covariant and add generics to protocols

Douglas Gregor dgregor at apple.com
Thu Jan 14 01:55:25 CST 2016


> On Jan 13, 2016, at 9:11 PM, Austin Zheng via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Hi Howard,
> 
>> On Jan 13, 2016, at 8:35 PM, Howard Lovatt <howard.lovatt at gmail.com <mailto:howard.lovatt at gmail.com>> wrote:
>> 
>> @ Austin,
>> 
>> Comments inline below.
>> 
>> On 14 January 2016 at 10:02, Austin Zheng <austinzheng at gmail.com <mailto:austinzheng at gmail.com>> wrote:
>> Hi Howard,
>> 
>> Making generics covariant by default would add even more of a burden to users. They would need to check the type property of a generic object any time they wanted to mutate that generic object or risk their program terminating.
>> 
>> Its a matter of balance, is that checking necessary. You certainly don't see it with Java arrays and this isn't a problem in practice. If you burden everyone with an annotation and in real programs that imperceptibly reduces the number of error then it was a bad call. The Java use case indicates that it is a bad call.
> 

[snip]

> 
>> 4. The length of a Swift array is not part of the type contract, whereas the type enclosed within a generic type is. Swift doesn't have fixed-length arrays.
>> 
>> Well it isn't in Swift, but in some languages it is. Proponents of these languages would point to a matrix math API and note how the compiler can check matrix size for conformance at compile time. Whereas in Swift that is a runtime check. The proponents would point to the inefficiency of runtime checking and the fact that you now need to test the code. I think the Swift team have taken the right approach and are saying typing the size of an array is not worth it.
> 
> There's a discussion going on right now about what it would take to add compile-time dimensional analysis to the language. Engineers have expressed interest in things like fixed-length arrays and non-type generic parameters to support exactly such a use case. I wouldn't read too much into what the current capabilities and limitations of Array mean and don't mean in terms of Swift's future design, although a core team member is free to pop in and clear things up again :).

Fixed-length arrays have come up a number of time in core team discussions, and would be a reasonable future extension to Swift. If nothing else, it’s important for C interoperability (have you seen the awful things we do when importing C structs containing array members?) and comes up almost immediately whenever someone talks about “low-level” Swift.

Non-type generic parameters is another thing that comes up from time to time in core team discussions. It’s usually motivated by fixed-length arrays (because *clearly* we would want to implement a fixed-size array in the standard library, not as a compiler hack, and that probably means non-type generic parameters), fixed-length vector types, or dimensional analysis. The discussion usually stops when we realize that we either need to design a model for constant expressions or admit some form of dependent typing in the language, both of which are major endeavors that are well out of scope for Swift 3.

>> Is that giving you an idea of where I am coming from?
> 
> I understand exactly where you are coming from. What I, and many other people in this thread are saying, is that we do not want to make the language less safe in order to spare the programmer the pain of typing out explicit variance annotations.
> 
> Arguing that some runtime checks for other things exist in Swift, therefore this unrelated specific thing should also be deferred to a runtime check, is not persuasive. Like you said, compile-time safety is a matter of balance; where I fall on that spectrum is a different point than where you fall. To you, the trade-off is worth it; to me (and the other people making the same arguments), it is not. To you, writing out your abstractions using associated types is painful; I haven't felt the same way.
> 
> In the end, this is just my opinion, I don't have any special veto powers, and I wouldn't want to bore you by restating my points again and again. If you can convince the core team and the other participants in the group that the trade-off is worth it and drive a proposal through to acceptance, all the better.

I don’t speak for the core team as a whole (i.e., we have not discussed this specifically), but my personal opinion aligns with what Austin is saying: this isn’t the right set of trade-offs for Swift.

I consider covariant generic parameters *by default* to be a non-starter. Outside of collection types, I wouldn’t expect the vast majority of generics to behave covariantly. Swift is fairly rigid about type soundness—if you’re going to do something that can break soundness, it’s going to require some explicit syntax involving a cast or *unsafe* component—so introducing this kind of runtime-enforced soundness by default would be going against the spirit of Swift.

As for opt-in variance, I’m predisposed against it (and I know of another core team member who doesn’t think it’s a worthwhile feature in a mainstream language). It’s not that it’s a foreign concept: we included variance in the Objective-C generics system because it was important for subtyping of the immutable collections. However, it’s important to note there that it is *not* the default: only immutable collections opt in to covariant generic parameters. The mutable collections (NSMutableArray, NSMutableSet, etc.) have invariant generic parameters, because we (a group that included the Swift core team) opted for a more sound static type-checking model (modulo Objective-C’s type erasure).

It’s also important to note that variance is not free in Swift the way it is in Java. If I have

  struct X<covariant T> { var t: T }

then someone coming from a Java background might expect that implicitly converting X<Int> to an X<Any> would effectively be free at runtime, because in Java an instance of T would always be an object and all objects have the same layout. However, this is not the case in Swift: an X<Int> directly stores an Int (1 word) while X<Any> directly stores an Any (4 words), so there’s a nontrivial cost in performing that conversion.

As I noted in my other reply, Swift’s value-semantic collections are covariant in their generic parameters, which we do through some fairly tight coupling between the compiler and standard library. From a theoretical standpoint, I’m very happy with the way value-semantic collections provide subtyping and mutation while maintaining soundness (== no runtime checks needed), and for me I would consider it “enough” if we were to formalize that compiler/collection type interaction with some kind of protocol so other collection types could opt in to subtyping, because I don’t think variance—as a language feature—carries its weight outside of the fairly narrow collection-subtyping cases. It would take a significant body of real-world evidence of important non-collection generic types that benefit from variance for me to reconsider my position against adding variance into the language.

From a practical standpoint, I’m not completely convinced that collection subtyping is all that important. I *love* the feature for its theoretical beauty with value semantics, but it has a cost (in complexity and in runtime performance) that I’m not sure is paid for by its uses in the real world. It’s something I’d like to investigate further before we dive into the generalization the feature for other collections.

	- Doug

[*] Accessing @objc members on AnyObject is the major exception that I can think of.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160113/ea0e24f3/attachment.html>


More information about the swift-evolution mailing list