为什么Set是一个函数?

时间:2012-01-06 13:53:49

标签: scala math

在Scala中,Set是一个函数:

trait Set[A] extends (A => Boolean)

这使得无法使用协变不可变Set,因为类型A出现在逆变位置。相反,Seq未定义为函数。已经有一些关于为什么以这种方式设计集合和序列的问题的内容:

一个答案说,其原因是数学背景。但这个答案没有多解释。那么,将Set定义为函数的具体优势是什么?如果以不同的方式实现它会有什么缺点?

2 个答案:

答案 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