在Scala中,Set
是一个函数:
trait Set[A] extends (A => Boolean)
这使得无法使用协变不可变Set
,因为类型A
出现在逆变位置。相反,Seq
未定义为函数。已经有一些关于为什么以这种方式设计集合和序列的问题的内容:
一个答案说,其原因是数学背景。但这个答案没有多解释。那么,将Set
定义为函数的具体优势是什么?如果以不同的方式实现它会有什么缺点?
答案 0 :(得分:11)
类型Set[A]
的集合必须有一个方法来测试类型A
中的元素是否在集合中。此方法(apply
)必须具有表示该元素的类型A
的参数,并且该参数处于逆变位置。这意味着集合在类型参数A
中不能协变。所以 - 不是函数接口的扩展使得不可能有协变的不可变集,而是存在逆变apply
方法。
为了方便起见,扩展Function1
接口以便能够传递集合并将它们视为函数是有意义的。
相比之下,序列抽象没有测试元素是否在序列中的方法,它只有索引方法 - apply
接受整数索引,并返回该索引处的元素。序列也被定义为函数,但类型为Int => A
的函数(在A
中是协变的),而不是A => Boolean
,作为集合。
如果您想了解更多关于如何在类型参数A
中将集合定义为协变来破坏类型安全性,请参阅此示例,其中集合实现由于缓存的原因而对私有成员进行了一些写入查找(@uV
下面是禁用方差检查的注释,而expensiveLookup
用于模拟对元素在集合中的计算成本高的检查的调用):
import annotation.unchecked.{uncheckedVariance => uV}
trait Set[+A] {
def apply(elem: A @uV): Boolean
}
class CachingSet[+A >: Null] extends Set[A] {
private var lastLookup: (A @uV, Boolean) = (null, false)
private def expensiveLookup(elem: A @uV) = (elem, true)
def apply(elem: A @uV): Boolean = {
if (elem != lastLookup._1) lastLookup = expensiveLookup(elem)
lastLookup._2
}
def lastQueriedElement: A = lastLookup._1
}
object Main extends App {
val css = new CachingSet[String]
val csa: CachingSet[AnyRef] = css
csa.apply(new AnyRef)
val s: String = css.lastQueriedElement // you'll get a ClassCastException here
}
答案 1 :(得分:6)
相比之下,Seq未定义为函数。
不正确。
Seq[T] extends (Int) => T