When using covariance notations or generic bounds in Scala
Asked Answered
L

2

11

In Scala variance can be defined with variance operators like + and - on the generic type argument. For example the List type is covariant in the standard library.

class List[+A]

So a function with a covariant list can be defined like this:

def foo[A](list : List[A])

Also variance can be emulated with generic bounds. So we can also write this

def foo[A](list : List[_:< A])

of course this makes no sense, because list is already covariant. But the same trick could be done for types that are not covariant. (like Stack). Of course, also a new types could be created from stack (inheritance of aggregation) that is covariant.

So my questions:

  1. When should be use generic bounds for variance? And when should we create a new covariant type?
  2. Are generic bounds only useful for variance, or can they declare more (language concepts).
  3. If they are only useful for variance, are bounds then only for compatibility with Java?

thx in advance :)

Ladylike answered 8/9, 2010 at 0:1 Comment(2)
I double this question - variance is the most harderst part of Scala and I actually don't understand it quite well. "Blah-blah covariant blah appeared in contravariant position blah-blah" :pContracted
I understood variance when I looked at the definitions of some of the Function traits (Function1, Function2, etc.) The function traits are all covariant on the output type and contravariant on the input (parameter) types. For example, a function of type Any => Unit can be used wherever a function of type String => Unit is expected, because the Any => Unit function can take a string as its input (because it can take anything as its input.) This is why the types of method parameters are a "contravariant position."Roaring
R
14

If a type is naturally covariant or contravariant you should declare it so. Your users will thank you for it. The use-site variance is indeed mostly there because of Java. More precisely, a type such as Array[T <: Number] is treated as a shorthand for an existential type:

ArrayBuffer[T] forSome { type T <: Number }

Existential types have a pretty bulky syntax in Scala. That's sort of intentional, because we do not recommend you use them much. When would you need an existential type?

  1. To write the analogue of a Java type with wildcards, such as List<? extends Number>.
  2. To write the analogue of a Java raw type, such as List.

In Java, raw types and wildcard types are not quite the same and neither is quite the same as an existential type (even though we know what they are not, it's pretty hard to state precisely what they are). But they are close enough to existentials in practice, so that Scala gets away with mapping them to this kind of type.

Rapturous answered 8/9, 2010 at 14:16 Comment(0)
S
6
  1. When creating a new generic type, say Foo[T], you should try hard to determine whether that type is covariant, contravariant or invariant and declare it Foo[+T], Foo[-T] or Foo[T] respectively. Admittedly this can be a bit difficult. However, it frees the user of Foo of making that decision every time she needs to use a Foo by using generic bounds. In short: prefer declaration site variance over call site variance when the variance is a property of the type itself.

BTW, the Programming in Scala book by Martin Odersky, Lex Spoon and Bill Venners has some great seactions about variance. See Chapter 19 Type Parametrization.

Spoilt answered 8/9, 2010 at 8:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.