Question

Sorry I have asked some questions like this one, but I still can't get a clear answer, maybe my bad English and unclear expression puzzled the kind people.

When I read the "Type Parameterization" in this article: http://www.artima.com/pins1ed/type-parameterization.html, I see there are some explanation about the type positions:

As a somewhat contrived example, consider the following class definition, where the variance of several positions is annotated with ^+ (for positive) or ^- (for negative):

   abstract class Cat[-T, +U] {
     def meow[W^-](volume: T^-, listener: Cat[U^+, T^-]^-)
     : Cat[Cat[U^+, T^-]^-, U^+]^+
   }

I can understand most of this class, except the W position. I don't understand why it marked as negative, and there is no explanation in the whole document.

It also says:

Type parameters annotated with + may only be used in positive positions, while type parameters annotated with - may only be used in negative positions.

How can I find a type with - annotation in position W to fit this negative position?

Was it helpful?

Solution

The language reference says:

  • The variance position of a method parameter is the opposite of the variance position of the enclosing parameter clause.
  • The variance position of a type parameter is the opposite of the variance position of the enclosing type parameter clause.
  • The variance position of the lower bound of a type declaration or type parameter is the opposite of the variance position of the type declaration or parameter.

OK what does it mean for a type parameter to have a variance position?

class Moo[+A, -B] {
  def foo[X] (bar : Y) ...

So Y is in a contravariant position, this is clear. We can put B in its position, but not A.

But what does it mean for X to be in a contravariant position? We cannot substitute A or B or anything there, it's just a formal parameter!

That's true, but this thing can have subordinate positions which are types, and have variance. So we need to count the position of X when tracking how many times we flip variance. There's no subordinate clauses of X here, but consider this:

class Moo[+A, -B] {
  def foo[X >: Z] (bar : B) ...

We probably can replace Z with either A or B, but which is correct? Well, the position of Z is the opposite of that of X, and the position of X is the opposite of that of the top-level, which is covariant, so Z must be covariant too. Let's check:

abstract class Moo[+A, -B] {
      def foo[X >: A] (bar : B)
}    
defined class Moo

Looks like we are right!

OTHER TIPS

There is a familiar example in the spec:

http://www.scala-lang.org/files/archive/spec/2.11/04-basic-declarations-and-definitions.html#variance-annotations

Sequence.append is example 4.5.2 in the pdf, but the markdown isn't numbered at the moment.

abstract class Sequence[+A] {
  def append[B >: A](x: Sequence[B]): Sequence[B]
}

In real life, see the doc for Seq.++, ignoring the "use case" and clicking on the "full signature" to show the lower bound.

This is the same pattern as in other widening operations like Option.getOrElse, where you're getting back a possibly wider type than you started with.

Here's an example of how it makes sense in terms of substitution:

Given a Seq[Fruit], I can append a Seq[Orange]. Since Apple <: Fruit, I can also append the oranges to a Seq[Apple] and get fruits back.

That's why the B type parameter wants to be bound by a covariant parameter. The variance position of B is classified as negative, for purposes of variance checking, but B itself is not annotated.

Funny thing is that this parses:

scala> trait X { def append[-](): Unit }
defined trait X
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top