我已经定义了一个抽象基类,如下所示:
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)
,其中
o1, o2
是Derived
f1
如下def f1(v1: Derived, v2: Derived): Int
现在,这给了我一个错误,因为myMethod
期望函数f1
为(Base, Base) => Int
,而不是(Derived, Derived) => Int
。但是,如果我将f1
的定义更改为(Base, Base) => Int
,那么它会给我一个错误,因为在内部我想使用SomeOtherClass
中的一些变量,Base
所做的参数没有。
答案 0 :(得分:3)
如果您希望能够使用 f1 函数 f1 ,那么 f1 必须属于同一类型(两者都是输入参数和返回值)或 f2 的子类。 Liskov Substitution Principle告诉我们,要使一个函数成为另一个函数的子类,它需要更少(或相同)并提供更多(或相同)。
因此,如果您有一个方法,作为参数采用类型(Fruit, Fruit) => Fruit
的函数,这里有一些有效函数的类型,您可以传递给该方法:
这涉及协方差/逆变规则;例如,Scala中的每个单参数函数都是一个具有两个类型参数Function2[-S, +T]
的特征。你可以看到它的参数类型和它的返回类型中的协变是逆变的 - 需要S
或更少(“更少”,因为它更通用,所以我们丢失信息)并提供T
或更多( “更多”,因为它更具体,所以我们获得更多信息。)
这会带给我们你的问题。如果你有相反的方法,试着让(Base, Base) => Int
符合预期的(Derived, Derived) => Int
,那就行了。方法myMethod
显然希望使用类型Derived
的值来提供此函数,并且采用类型Base
的值的函数将很乐意接受这些值;毕竟,Derived
是Base
。基本上myMethod
所说的是:“我需要一个可以处理Derived
s的函数”,任何知道如何使用Base
的函数也可以使用它的任何子类,包括Derived
。
其他人已指出您可以将函数f
的参数类型设置为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()
可用,它执行所需的逻辑“。并且由于逻辑可以基于类型具有不同的实现,因此这是ad-hoc多态的情况,类型类是处理它的一种很好的方式。
这里很酷的是,当引入一个具有自己的f
实现的新类型时,您只需要将此实现作为隐式值放在Base
伴随对象中,这样就可以了可用于myMethod
。方法myMethod
本身保持不变。
答案 1 :(得分: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
答案 2 :(得分: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)