1

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

abstract class Base() {
    val somevariables
}

然后,我扩展了这个类,如下所示:

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

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

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

我想使用上述方法myMethod(o1, o2, f1),其中

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

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

4

3 回答 3

3

如果您希望能够在需要函数f2的地方使用函数f1,则f1必须是相同类型(输入参数和返回值)或f2的子类。里氏替换原则告诉我们,一个函数要成为另一个函数的子类,它需要更少(或相同)并提供更多(或相同)。

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

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

这与协变/逆变规则有关;例如,Scala 中的每个单参数函数都是具有两个类型参数的 trait Function2[-S, +T],. 你可以看到它的参数类型是逆变的,而它的返回类型是协变的——需要S或更少(“更少”,因为它更通用,所以我们丢失了信息)和提供T或更多(“更多”,因为它更具体,所以我们获取更多信息)。

这给我们带来了您的问题。如果你有相反的东西,试图适应预期(Base, Base) => Int的地方,那会奏效。(Derived, Derived) => IntMethodmyMethod显然希望为这个函数提供 typeDerived的值,而接受 type 值的函数Base会很乐意接受这些值;毕竟Derived是一个Base。基本上myMethod说的是:“我需要一个可以处理Deriveds 的函数”,任何知道如何使用Bases 的函数也可以采用它的任何子类,包括Derived.

其他人指出,您可以将 functionf的参数类型设置为 的子类型Base,但在某些时候您可能希望将 v1 和 v2 与该函数一起使用,然后您需要通过模式匹配恢复为向下转换. 如果您对此感到满意,您也可以直接在函数上进行模式匹配,试图找出它的真实性质。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:“我需要两个 T 类型的值,并且我需要Base[T]在范围内有一个隐式可用的值(这就是[T: Base]部分,这是一种奇特的说法,你需要一个类型的隐式参数Base[T];这样你将通过其名称访问它,这样您就可以通过implicitly) 访问它。然后我知道我将拥有f()执行所需逻辑的可用"。而且由于逻辑可以根据类型有不同的实现,这是一种特殊多态性的情况,类型类是处理这种情况的好方法。

这里很酷的是,当引入了具有自己实现的新类型时f,您只需将此实现Base作为隐式值放入伴随对象中,以便它可以用于myMethod. 方法myMethod本身保持不变。

于 2017-04-28T19:57:26.527 回答
2

您应该使用类型参数来确保类型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
于 2017-04-28T20:27:55.897 回答
1

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

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)
于 2017-04-28T20:13:41.700 回答