1

If I write Foo[_ <: Bar] or Foo[+T <: Bar] what does the latter let me do, that I could not do with the former?

Is it just convenience, so that can write def bar: T rather than def bar: Bar?

In which context is it useful?

Is it actually accurate to say that there is no variance in java? Can one not model it with <? extends Foo> and <? super Bar>?

Dima
  • 39,570
  • 6
  • 44
  • 70

2 Answers2

2

The type bounds control what types are valid parameters. The covariance/contravariance annotations control the sub/super-typing relationship between instances with different parameters. Foo[+T] means Foo[A] is a subtype of Foo[B] if A is a subtype of B. Foo[-T] means the reverse.

class Foo1[T <: Bar]()
class Foo2[+T <: Bar]()
trait Bar
trait Baz extends Bar

val f1: Foo1[Bar] = new Foo1[Baz]() // compile error
val f2: Foo2[Bar] = new Foo2[Baz]() // works just fine

One benefit as opposed to using type boundaries like Foo1[_ <: Bar] everywhere is the compiler will enforce certain properties on the class itself. For instance, this won't compile:

class Foo[+T]() {
  def f(t: T): Unit = {}
}

Neither will this:

class Foo[-T]() {
  def f(): T = { ??? }
}

As far as I know, Java has no way to explicitly represent covariance or contravariance. This has led to a lot of bugs, especially with arrays being implicitly covariant even though they shouldn't be since they are mutable.

Joe K
  • 18,204
  • 2
  • 36
  • 58
  • I think they meant something like `def func(x: Foo[_ <: Bar])` as a way to simulate variance with existentials. – HTNW Jul 12 '17 at 23:38
  • ah ok, I think I understand the context a bit more now. Edited to include more explanation. – Joe K Jul 12 '17 at 23:50
  • Arrays being covariant while they shouldn't is beyond the point, it's just a java design bug. The question is what use case does `Foo[+T]` address, that `Foo[_]` does not – Dima Jul 13 '17 at 00:38
  • Agree it's a bit of a digression, but you did ask about variance in Java. It was meant as an example of the usefulness of scala variance and the messes it's possible to create without it :) – Joe K Jul 13 '17 at 00:43
  • @JoeK I don't see what one has to do with the other. Arrays should not be covariant in java, they are simply modeled wrong. The question isn't about java libraries so much as about the language in general. I can make `ImmutableList extends Object>` in java.The question is whether that definition is inferior to scala's `immutable.List[+T]`, and, if so, in which way – Dima Jul 13 '17 at 01:00
  • I think it's mainly a question of whether the class itself enforces the variance behavior or whether all users of the class enforce their own variance behavior. If you write a non-variant `List[T]` library, then all other users must have their types be `List[X <: T]`. If, for instance, someone accidentally defines a `def foo(xs: List[AnyVal])` method, then there is no way to pass a `List[Double]` to it without changing the signature. Not so if it's defined as `List[+T]`. If you have complete control over all classes and methods, then they are probably equivalently powerful. – Joe K Jul 13 '17 at 01:18
  • Well, if they "accidentally" define it as `foo(xs: List[AnyRef]` or `foo(xs: String)` then covariance won't help to pass in the list of Doubles anyway :) – Dima Jul 13 '17 at 01:54
1

Is it actually accurate to say that there is no variance in java? Can one not model it with <? extends Foo> and <? super Bar>?

Java is usually said to have use-site variance, as opposed to Scala declaration-site variance (well, really Scala supports both). It is actually more expressive, strictly speaking: you can write more sensible programs, e.g. methods which don't put anything into a List may be covariant while methods which put but don't look at the contents may be contravariant. With declaration-site variance you instead need to have separate immutable and mutable types.

A well-known symptom of the problem is the signature of Set#contains in Scala, which can't just accept A without forcing Set to be invariant.

The problem with having only use-site variance is that it complicates the signatures of all methods working with the type if you want to be consistent: not just the ones declared on the type itself, but those which call them.

See also How does Java's use-site variance compare to C#'s declaration site variance? and https://kotlinlang.org/docs/reference/generics.html#variance.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487