为啥多态函数不能在 Scala 中接受通配符(存在)类型?

Posted

技术标签:

【中文标题】为啥多态函数不能在 Scala 中接受通配符(存在)类型?【英文标题】:Why can't a polymorphic function accept wildcard (existential) types in Scala?为什么多态函数不能在 Scala 中接受通配符(存在)类型? 【发布时间】:2021-12-02 01:30:06 【问题描述】:

在下面的示例中,我想知道为什么 funPoly 不能接受存在量化类型值 outersFromInnersEx,尽管 funEx 可以。

case class InnerCassClass[I, E, O](i: I, e: E, o: O)
case class OuterCaseClass[I, E, O](inner: InnerCassClass[I, E, O])


val inner1 = InnerCassClass(5, "foo", 3.3f)
val inner2 = InnerCassClass(4.4f, 6, "bar")


// Doesn't work as expected due to invariance of type paramemters I, E, O (but confirm)
// val outersFromInnersAny: List[OuterCaseClass[Any, Any, Any]] = List(inner1, inner2).map(OuterCaseClass.apply)

val outersFromInnersEx: List[OuterCaseClass[_, _, _]] = List(inner1, inner2).map(OuterCaseClass.apply)

def funPoly[I, E, O](occ: List[OuterCaseClass[I, E, O]]): Unit = ()
def funEx(occ: List[OuterCaseClass[_, _, _]]): Unit = ()


// This doesn't work, but why?
val u1 = funPoly(outersFromInnersEx)

val u2 = funEx(outersFromInnersEx)

注意,我在 Scala 3 (try online) 中对此进行了测试,但问题在 Scala 2 中基本相同,尽管此特定示例在 Scala 2 中存在其他问题。

【问题讨论】:

【参考方案1】:

请注意,这两种是非常不同的类型:

def funPoly[I, E, O](occ: List[OuterCaseClass[I, E, O]]): Unit = ()
def funEx(occ: List[OuterCaseClass[_, _, _]]): Unit = ()

第一个能够处理OuterCaseClass 对象的统一列表,其中每个对象的类型参数都相同。第二个可以处理OuterCaseClass 对象的混合列表,其中每个对象的类型参数(可能)不同。

使类型参数协变“修复”问题,因为 List[OuterCaseClass[_, _, _]] 等效于 List[OuterCaseClass[Any, Any, Any]],您可以简单地将 funPolys 类型参数实例化为 Any, Any, Any 以使其接受 funEx 可以接受的任何内容.

一般来说,您可以将存在限定的类型传递给多态函数。例如,这应该有效:

case class ListAndFunction[A](list: List[A], function: A => Int)
val a: ListAndFunction [_] = ListAndFunction[String](List("a"), _.length)
def mapFunction[A](a: ListAndFunction [A]): List[Int] =
  a.list.map(a.function)
mapFunction(a)

【讨论】:

有趣 - 我曾(显然是错误地)假设类型系统仍然能够计算 outersFromInnersEx 的基础类型(并且根据类型检查器,它将具有新的类型参数每个旧类型参数的交集(例如在我的示例中I = Float & Int);最坏的情况我猜这将是Any。但听起来这不是类型系统的工作方式。除此之外,你的解释在这里了解协方差的工作原理很有帮助。 无论如何,这里可能不需要进一步澄清,这个其他 SO 线程可能描绘了一幅很好的画面,我明天会深入探讨 ***.com/questions/15186520/…【参考方案2】:

关于type variances,你可以通过改变使funPoly工作

case class InnerCassClass[I, E, O](i: I, e: E, o: O)
case class OuterCaseClass[I, E, O](inner: InnerCassClass[I, E, O])

case class InnerCassClass[+I, +E, +O](i: I, e: E, o: O)
case class OuterCaseClass[+I, +E, +O](inner: InnerCassClass[I, E, O])

【讨论】:

这是否暗示接受通配符类型的函数可能不安全,因为它们忽略了方差?我仍然看不到方差和存在通配符类型之间的关系。

以上是关于为啥多态函数不能在 Scala 中接受通配符(存在)类型?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Scala 语言要求您初始化实例变量而不是依赖默认值?

当 Row 接受可变参数时,为啥 Scala 编译器会失败并显示“此处不允许 ': _*' 注释”?

Scala多态函数类型不匹配

为啥 DataFrame 在 spark 2.2 中仍然存在,甚至 DataSet 在 scala 中也提供了更多的性能? [复制]

Scala - 抽象继承多态

为啥不能在 UDF 中访问数据框? [Apache Spark Scala] [重复]