Make generics covariant and add generics to protocols


#1

It seems that this thread discusses only "Make generics covariant" and not so "add generics to protocols". Should the latter one be separated into a new thread?

- Maximilian


(Howard Lovatt) #2

1st sorry for not replying sooner - work go it in the way!

At the suggestion of Maximilian I have split the discussion into two parts, this part is Make generics covariant.

Most people on this forum don't like the runtime type check I proposed for writing to a covariant generic. Therefore I propose a modification; a generically typed argument is covariant if it is a let, e.g.:

    struct Box<T> {
        var value: T
        init(_ value: T) { self.value = value }
    }
    let intBox = Box(1)
    let floatBox = Box(2.0)
    func boxesPrint(box1: Box<Any>, box2: Box<Any>) {
        print(box1.value)
        print(box2.value)
    }
    boxPrint(intBox, floatBox) // OK, covariant let (box1 and box2)

A var is invariant and if you want to convert from a let to a var you need to type cast, e.g.:

    func boxesIncrement(box1: Box<Any>, box2: Box<Any>) -> (Box<Any>, Box<Any>) {
        var box1Int = box1 as! Box<Int> // Cast needed, Any is really an Int
        box1Int.value += 1
        var box2Float = box2 as! Box<Double> // Cast needed, Any is really a Double
        box2Float.value += 1.0
        return (box1Int, box2Float)
    }

Notes:
  1. inout parameters would be invariant
  2. Classes would need to behave like structs and have mutable annotation on methods that write to their fields so that mutating methods are disallowed in lets (this would make classes more consistent with structs and protocols)
  3. The implementation method previously presented would allow this type casting since each instance of Box would have field BoxT that identifies its actual type.

Currently Swift *Arrays* of *classes* allow covariance, but not generics in general or protocols (as pointed out by Douglas Gregor, but otherwise not well known or well documented). This array/class specific mechanism would be removed if generics were made covariant, since this proposed covariance would subsume this special case covariance.

Douglas Gregor has, quite reasonably, also asked for evidence for the usefulness of covariance. In Java it is easy to see where variance is used because it needs to be annotated. If the Java Collections API, i.e. functions that act on a Collection, is studied (https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html) then 25 of 66 functions use covariance (i.e. would be covered by this proposal).

Contravariance is mainly used in Java and other languages when function types are represented by an interface, e.g. the Java interface Function has a method:

<V> Function<V,R> compose(Function<? super V,? extends T> before)

Note the type of before; it's a function, but it is necessary to annotate the contravariance of the input and covariance of the output. In Swift that is the behaviour of function types already and therefore the major use case for contravariance can be ignored. Also note how cluttered the declaration is because of the variance annotation.

However there are 4 other use cases for contravariance in that API, functions like:

static <T> void fill(List<? super T> list, T obj)

Note how the argument list has a contravariance generic, i.e. the element type of the list to be filled can be a super type of the elements placed in the list. This usage is not covered by either current Swift or the proposal.

The use of covariance, 25 of 66, greatly outnumbers contravariance of the non-function type, 4 of 66, hence it is proposed that this minor use case is not covered (as at present).

A number of people have asked about the Java experience with annotated variance. When Oracle designed Java 8 in an open source manner there was much discussion of how variance notation clouded the intent of code and a simpler system (like that proposed above or indeed the present Swift system) would be desirable. An example of this sentiment was expressed by Josh Bloch, well known in the Java world as the main designer of Java's collection API, who stated when promoting both his book and the latest version of Java:

    "Generics certainly improved the type safety and expressiveness of the language, and I'm very glad they were added. But they haven't been an unqualified success. You only have to peruse Angelika Langer's 513-page Java Generics FAQs to appreciate this. So I wish we'd been able to simplify the design."
    (http://www.oracle.com/technetwork/articles/java/bloch-effective-08-qa-140880.html)

In summary, all that is needed in Swift is covariance of generic parameters since covariance also covers invariance and contravariance is mainly covered by function types already.

···

On 17 Jan 2016, at 9:16 AM, Maximilian Hünenberger via swift-evolution <swift-evolution@swift.org> wrote:

It seems that this thread discusses only "Make generics covariant" and not so "add generics to protocols". Should the latter one be separated into a new thread?

- Maximilian
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution