scala逆变位置的方法

时间:2014-09-29 21:36:18

标签: scala scala-2.10

我正在阅读http://oldfashionedsoftware.com/2008/08/26/variance-basics-in-java-and-scala/

正在查看代码

class CoVar[+T](param1: T) {
  def method1(param2: T) = { }
  def method2: T = { param1 }
  def method3: List[T] = { List[T](param1) }
  def method4[U >: T]: List[U] = { List[U](param1) }
  val val1: T = method2
  val val2: Any = param1
  var var1: T = method2
  var var2: Any = param1
}

那么如果我有一个

val covar1 = new CoVar(new Car)
val covar2: CoVar[Vehicle] = covar1 //completely legal with covariant

现在,让我们来看看方法

  • 方法1 - 我不知道为什么这不编译,这是我的主要问题
  • 方法2 - param1是一辆汽车,而method2返回一辆因车辆是车辆而罚款的车辆
  • method3 - 因为返回了List [Vehicle]而Car是一个Vehicle,这很好
  • var1 - 同样的问题,我相信并没有太大的不同

我认为这对于method1(param:Vehicle)来说还可以,因为我可以传递一辆新的车辆就好或者新车很好

但是原始的CoVar类没有编译,因为它说method1是逆变位置。我认为逆变意味着我可以通过

现在,用ContraVar和方法1再次讨论这个问题

class ContraVar[-T](param1: T) {
    def method1(param2: T) = { }
    val val2: Any = param1
    var var2: Any = param1
}

val temp1 = new ContraVar(new Car)
val temp2: ContraVar[Ford] = temp1

temp2.method1(new Ford)
temp2.method1(new FordMustang)
temp2.method1(new Car) //fails to compile(good)

哪个工作得很好。有人可以解释为什么method1在CoVar上打破了吗?也许我正在走向完全错误的道路,因为让method1编译得很好会出错?

感谢, 迪安

3 个答案:

答案 0 :(得分:2)

您的问题归结为以下为何非法

trait Tool[+A] {
  def treat(c: A): Unit
}

想象一下它会编译......让我们从外面看一个用例:

def apply[A](tool: Tool[A], car: A): Unit = tool.treat(car)

假设A有两种可能的类型:

trait Car
trait Mustang extends Car { def awe(): Unit }

协方差意味着Tool[Mustang] <: Tool[Car]。因此,每当要求Tool[Car]时,您都可以使用Tool[Mustang]。现在想象一下Tool[Mustang]

val tm = new Tool[Mustang] { def treat(m: Mustang) = m.awe() }

现在你可以致电:

apply[Car](tm, new Car {})

这意味着,tm可以访问通用汽车中不存在的方法awe。显然这不是一种声音类型的关系。 因此,只要在参数位置使用类型,它就必须是不变的或逆变的。

答案 1 :(得分:1)

让我们给你的方法1一个身体:

class CoVar[+T] {
    var listOfT: List[T] = Nil

    // method1 prepends the given element to listOfT
    def method1(param2: T) = { listOfT = param2 :: ListOfT }
}

现在我们实际上对传递给method1的参数做了一些事情,如果允许的话,很容易看出会出现什么问题。

// Lets construct one of these that holds Ints. and add something to it
val covarInt = new CoVar[Int]
covarInt.method1(1)

// lets assign to a more general value, this is no problem because of the covariance
val covarAny: CoVar[Any] = covarInt

// now lets do a bad thing:
covarAny.method1("this is a string not an Int")

最后一行显示了允许的坏事。由于covarAnyCoVar[Any]类型,这意味着在CoVar类中type T = Any,因此预期作为method1输入的类型为Any ,因此StringString,因此应该允许将Any传递给此函数。然而,方法体将尝试将我们传递的String添加到Int的列表中,这不应该被允许。

答案 2 :(得分:0)

我有一个新的答案,我认为帮助我很多为什么params是逆变而不是协变。这个例子也可以在没有泛型的情况下完成。

让我们使用函数对象来定义Animal,Bird和Duck类

class Animal {
   def makeSound() = "animalsound"
   def walk()
}
class Bird extends Animal {
   def makeSound() = "tweeeeet"
   def fly()
}
class Duck extends Bird {
   def makeSound() = "quack"
   def paddle()
}

现在,让我们尝试定义一个协变函数

val doSomething: (Bird => String) = { d:Duck => d.paddle() }

当然,当一只小鸟被传入时,我们不能施放给鸭子,因为它可能不是鸭子,而且在鸟类中它肯定不会用桨法进行划桨。

现在,让我们尝试定义一个逆变函数

val doSomething: (Bird => String) = { a:Animal => a.walk() }

这现在编译并且有效,因为鸟是动物所以它将有一个步行方法。这对我来说真的很清楚,自然方法与函数非常相似,或者你总是可以将方法转换为函数,在这种情况下,它需要对params进行逆变。

现在,还有一种方法我们可以在这里做一些事情,如T&gt;:鸟可能吗?我想知道我是否要做一个(T&gt;:Bird =&gt; String)= .....

好吧,我认为我现在对此并不了解,并将其作为TODO留在我要了解的事项清单上。