【问题标题】:Scala Variance Concept, why it doesn't compileScala Variance Concept,为什么它不编译
【发布时间】:2018-07-05 11:23:35
【问题描述】:

我是 Scala 的菜鸟,所以请不要投反对票。

class MyClass extends AnyRef
class MySubClass extends MyClass

val af0: (Seq[_]) => Boolean = (s) ⇒ { s eq null }

val f4: (MySubClass) => Boolean = (s) => { s eq null }

val af2: (List[_]) => Boolean = af0 //(Line 1)
val f7: MyClass => Boolean = f4 //(Line 2)

为什么第 (1) 行编译而第 (2) 行不编译?对我来说,它们都与序列相同,是列表的子类型。如何让它发挥作用?就像第 1 行一样?

https://docs.scala-lang.org/tutorials/FAQ/collections.html [列出对象层次结构]

【问题讨论】:

标签: scala concept


【解决方案1】:

您看到的内容称为contravariance。因此,函数参数需要是逆变的:

class HisSubClass extends MyClass

val his = new HisSubClass 
f7(his) // his is accepted as MyClass

现在f4 将被调用,而不是MySubClass,这将是错误的。

Seq / List 的情况是有效的,因为它是相反的。 ListSeq 的子类。

val af2: (List[_]) => Boolean = af0

就像

val aff0: (MyClass) => Boolean = (s) ⇒ { s eq null }
val aff2: (MySubClass) => Boolean = aff0

承诺(合同)

对我理解参数/返回值差异有很大帮助的是将类型声明视为承诺(或合同)。返回值是协变的,因为您已承诺您的返回值将是MyClass 类型,并且通过在子类中提供MySubClass,您仍然信守诺言。承诺您将接受 MyClass 类型的参数,然后尝试声明仅接受 MySubClass 的子类成员意味着尝试缩小承诺范围,这是您无法做到的(子类必须完全实现父类)。

f4 中的示例中,您已承诺将给函数MySubClass 作为参数。当您尝试将其分配给 f7 时,您是在试图打破这个承诺,因为您可以通过 f7 调用任何 MyClassf4

【讨论】:

    【解决方案2】:

    因为您尝试将Function1[MyClass, Boolean] 类型的值分配给Function1[MyClass, Boolean] 类型的值,但Function1 的第一个类型参数是逆变的,请参阅API doc

    trait Function1[-T1, +R] extends AnyRef

    但它允许你这样做:

    val f7: MyClass => Boolean = s => s eq null 
    val f44: (MySubClass) => Boolean = f7
    

    你可以找到差异解释here

    【讨论】:

    • 那么为什么根据问题编译第(2)行?
    • 因为SeqList 的超类型,而不是子类型。如果您在示例中交换 SeqList,那么它将等同于您对 MyClass 所做的操作,并且该行也不会编译。
    猜你喜欢
    • 2020-12-16
    • 2014-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-21
    • 1970-01-01
    相关资源
    最近更新 更多