【问题标题】:Why is a Set a function?为什么 Set 是一个函数?
【发布时间】:2012-01-06 13:53:48
【问题描述】:

在 Scala 中,Set 是一个函数:

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

这使得不可能有一个协变不可变 Set 因为类型 A 出现在逆变位置。相比之下,Seq 没有定义为函数。关于为什么 Sets 和 Seqs 是这样设计的问题,已经有一些内容了:

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

【问题讨论】:

  • 我认为this answer 对您链接的第一个问题说的比其他问题要多。
  • 顺便说一句,为什么如果我在第一个链接问题中将函数foo 称为 foo(Set("asd")),它会编译?
  • 我认为这是因为SetFactory 中的apply 方法足够灵活,可以生成仅包含参数的不同元素类型集。

标签: scala math


【解决方案1】:

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
}

【讨论】:

    【解决方案2】:

    相比之下,Seq 没有​​被定义为函数。

    不正确。

    Seq[T] extends (Int) => T
    

    【讨论】:

    • 这令人惊讶。我没有注意到,因为类型参数不会出现在逆变位置。细节不错!
    • 你说得对,它们扩展了部分功能。我已经更正了。
    猜你喜欢
    • 2020-07-04
    • 1970-01-01
    • 2012-08-04
    • 2022-12-07
    • 2019-01-16
    • 2019-10-05
    • 1970-01-01
    • 2021-10-16
    • 2022-11-17
    相关资源
    最近更新 更多