扩展 class 时 Scala 类型冲突

Scala conflict of types when extending a class

我定义了一个抽象基础 class,如下所示:

abstract class Base() {
    val somevariables
}

然后,我将 class 扩展如下:

case class Derived (a: SomeOtherClass, i: Int) extends Base {
//Do something with a
} 

然后,我有一个方法(独立于classes)如下:

 def myMethod (v1: Base, v2: Base, f:(Base, Base) => Int ): Int

而我想用上面的方法作为myMethod(o1, o2, f1),其中

  1. o1, o2Derived
  2. 类型的对象
  3. f1如下def f1(v1: Derived, v2: Derived): Int

现在,这给了我一个错误,因为 myMethod 期望函数 f1(Base, Base) => Int,而不是 (Derived, Derived) => Int。但是,如果我将 f1 的定义更改为 (Base, Base) => Int,那么它会给我一个错误,因为在内部我想使用来自 SomeOtherClass 的一些变量,这是 Base 的一个参数没有。

如果您希望能够使用函数 f1,其中需要函数 f2f1 必须是同一类型(输入参数和 return 值)或 f2 的子类。 Liskov Substitution Principle 告诉我们,一个函数要成为另一个函数的子类,它需要更少(或相同)并提供更多(或相同)。

因此,如果您有一个方法作为参数接受类型为 (Fruit, Fruit) => Fruit 的函数,以下是您可以传递给该方法的一些有效函数的类型:

  • (水果, 水果) => 水果
  • (水果, 水果) => 苹果
  • (任意,任意)=> 水果
  • (任何,任何)=> 苹果

这与covariance/contravariance规则有关;例如,Scala 中的每个单参数函数都是一个具有两个类型参数 Function2[-S, +T] 的特征。您可以看到它在其参数类型上是逆变的,在其 return 类型上是协变的 - 需要 S 或更少("less" 因为它更通用,所以我们丢失了信息)并提供 T或更多("more"因为它更具体,所以我们得到更多的信息)。

这给我们带来了您的问题。如果您有相反的方法,尝试将 (Base, Base) => Int 放在预期 (Derived, Derived) => Int 的位置,那将起作用。方法 myMethod 显然期望为该函数提供 Derived 类型的值,而采用 Base 类型值的函数将很乐意接受这些值;毕竟,Derived 是一个 Base。基本上 myMethod 的意思是:"I need a function that can handle Deriveds",任何知道如何使用 Bases 的函数也可以接受它的任何子 类,包括 Derived.

其他人指出,您可以将函数 f 的参数类型设置为 Base 的子类型,但在某些时候您可能希望将 v1 和 v2 与该功能,然后您将需要通过模式匹配恢复为向下转换。如果你对此没意见,你也可以直接在函数上进行模式匹配,试图弄清楚它的真实本质。无论哪种方式,模式匹配在这种情况下都很糟糕,因为每次引入新类型时你都需要 fiddle 大约 myMethod

下面是如何用类型类更优雅地解决它:

trait Base[T] {
  def f(t1: T, t2: T): Int
}

case class Shape()
case class Derived()

object Base {

  implicit val BaseDerived = new Base[Derived] {
    def f(s1: Derived, s2: Derived): Int = ??? // some calculation
  }

  implicit val BaseShape = new Base[Shape] {
    def f(s1: Shape, s2: Shape): Int = ??? // some calculation
  }

  // implementations for other types
}

def myMethod[T: Base](v1: T, v2: T): Int = {
  // some logic
  // now let's use f(), without knowing what T is:
  implicitly[Base[T]].f 
  // some other stuff
}

myMethod(Shape(), Shape())

这里发生的是 myMethod 说:"I need two values of some type T and I need to have an implicit Base[T] available in scope (that's the [T: Base] part, which is a fancy way of saying that you need an implicit parameter of type Base[T]; that way you would access it by its name, and this way you access it via implicitly). Then I know I will have f() available which performs the needed logic"。由于逻辑可以根据类型有不同的实现,这是一种临时多态性的情况,类型 类 是处理这种情况的好方法。

这里很酷的是,当引入一个新类型时,它有自己的 f 实现,你只需要将这个实现作为隐式值放在 Base 伴随对象中,所以它可供 myMethod 使用。方法 myMethod 本身保持不变。

根据我(非常简单)的测试,这个变化...

def myMethod[B <: Base](v1: Base, v2: Base, f:(B, B) => Int ): Int = ???

...将允许这些方法中的任何一种...

def f1(a: Derived, b:Derived): Int = ???
def f2(a: Base, b:Base): Int = ???

...被接受为传递参数。

myMethod(Derived(x,1), Derived(x,2), f1)
myMethod(Derived(x,1), Derived(x,2), f2)

您应该使用类型参数来确保 myMethod 中的类型正确排列。

def myMethod[B <: Base](v1: B, v2: B)(f: (B, B) => Int): Int

或者更笼统一点:

def myMethod[B <: Base, A >: B](v1: B, v2: B)(f: (A, A) => Int): Int