【问题标题】:Why is Scala's immutable Set not covariant in its type?为什么 Scala 的不可变 Set 在其类型上不是协变的?
【发布时间】:2009-03-24 09:11:01
【问题描述】:

编辑:根据原始答案重写此问题

scala.collection.immutable.Set 类的类型参数不是协变的。为什么是这样?

import scala.collection.immutable._

def foo(s: Set[CharSequence]): Unit = {
    println(s)
}

def bar(): Unit = {
   val s: Set[String] = Set("Hello", "World");
   foo(s); //DOES NOT COMPILE, regardless of whether type is declared 
           //explicitly in the val s declaration
}

【问题讨论】:

  • 值得注意的是foo(s.toSet[CharSequence]) 编译得很好。 toSet 方法是 O(1) - 它只是包装 asInstanceOf
  • 还要注意 foo(Set("Hello", "World")) 在 2.10 上也可以编译,因为 Scala 似乎能够推断出正确的 Set 类型。但它不适用于隐式转换 (stackoverflow.com/questions/23274033/…)。

标签: scala set covariance scala-collections


【解决方案1】:

Set 在其类型参数中是不变的,因为集合背后的概念是函数。以下签名应该稍微澄清一下:

trait Set[A] extends (A=>Boolean) {
  def apply(e: A): Boolean
}

如果 SetA 中是协变的,则由于函数的逆变性,apply 方法将无法采用 A 类型的参数。 Set 可能在A逆变,但是当您想要执行以下操作时,这也会导致问题:

def elements: Iterable[A]

简而言之,最好的解决方案是保持事物不变,即使对于不可变的数据结构也是如此。您会注意到immutable.Map 在其类型参数之一中也是不变的。

【讨论】:

  • 我猜这个论点取决于“集合作为函数背后的概念”——这可以扩展吗?例如,“作为函数的集合”给我的“作为集合的集合”没有什么优势?是否值得放弃使用该协变类型?
  • 类型签名是一个相当弱的例子。集合的“应用”与它的包含方法相同。唉,Scala 的 List 是协变的,并且也有一个 contains 方法。 List 的 contains 的签名当然是不同的,但该方法的工作原理与 Set 的一样。因此,除了设计决策之外,没有什么能真正阻止 Set 协变。
  • 从数学角度看,集合不是布尔函数。集合是根据 Zermelo-Fraenkel 公理“构建”的,而不是通过某些包含函数减少。这背后的原因是罗素悖论:如果任何东西都可以成为集合的成员,那么请考虑不是自身成员的集合的集合 R。然后问问题,R是R的成员吗?
  • 我仍然不相信牺牲协方差对 Set 来说是值得的。当然,它是一个谓词很好,但你通常可以稍微详细一点,使用“set.contains”而不是“set”(可以说,“set.contains”在许多情况下读起来更好)。
  • @Martin:因为List的contains方法是Any,而不是A。List(1,2,3).contains _的类型是(Any) => Boolean,而Set(1,2,3).contains _的类型是res1: (Int) => Boolean
【解决方案2】:

http://www.scala-lang.org/node/9764Martin Odersky 写道:

“关于集合的问题,我相信不变性也源于实现。通用集合被实现为哈希表,它是键类型的不变数组。我同意这是一个有点烦人的不规则性。”

因此,我们所有为此构建原则性理由的努力似乎都被误导了 :-)

【讨论】:

  • 但是有些序列也是用数组实现的,而且Seq 仍然是协变的……我错过了什么吗?
  • 这可以通过在内部存储Array[Any] 轻松解决。
  • @rightfold 是正确的。可能有一个合理的原因,但事实并非如此。
【解决方案3】:

编辑:对于任何想知道为什么这个答案似乎有点离题的人来说,这是因为我(提问者)已经修改了这个问题。

Scala 的类型推断足以确定在某些情况下您需要 CharSequences 而不是 Strings。特别是,以下内容在 2.7.3 中对我有用:

import scala.collections.immutable._
def findCharSequences(): Set[CharSequence] = Set("Hello", "World")

关于如何直接创建 immutable.HashSets:不要。作为实现优化,少于5个元素的immutable.HashSets实际上并不是immutable.HashSet的实例。它们是 EmptySet、Set1、Set2、Set3 或 Set4。这些类是 immutable.Set 的子类,但不是 immutable.HashSet。

【讨论】:

  • 你是对的;在试图简化我的实际示例时,我犯了一个小错误:-(
猜你喜欢
  • 1970-01-01
  • 2015-04-01
  • 2017-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-24
相关资源
最近更新 更多