扩展 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)
,其中
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
的一个参数没有。
如果您希望能够使用函数 f1,其中需要函数 f2,f1 必须是同一类型(输入参数和 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 Derived
s",任何知道如何使用 Base
s 的函数也可以接受它的任何子 类,包括 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
我定义了一个抽象基础 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)
,其中
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
的一个参数没有。
如果您希望能够使用函数 f1,其中需要函数 f2,f1 必须是同一类型(输入参数和 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 Derived
s",任何知道如何使用 Base
s 的函数也可以接受它的任何子 类,包括 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