Why the type position of a method is marked as negative?

江枫思渺然 提交于 2019-12-01 08:44:36

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!

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
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!