1

我有一个类型 Foo,有子类型。

我有另一个类,其中包含一个 Foo:

class FooResult(val foo: Foo ...

我有一组 FooResult,我希望用 map() 将 Foos 从中拉出,然后计算该 Set (Foos) 和另一组 Foos 之间的差异。下面的“结果”是一个 Set[FooResult] 并且 - 这是关键部分 - bundle 是一个 Set[_ <: Foo]

val completedFoos = results.map(result => result.calc)
val unfinishedFoos = bundle.foos.diff(completedCalcs)

第二行不会编译。当 bundle 是 Set[Foo] 而不是 Set[_ <: Foo] 时,它工作得很好——引入协方差把事情搞砸了。这是错误:

type mismatch;
found   : Set[Foo]
required: scala.collection.GenSet[_$1]
Note: Foo >: _$1, but trait GenSet is invariant in type A.
You may wish to investigate a wildcard type such as `_ >: _$1`. (SLS 3.2.10)

我还没有找到任何简单的方法来解决这个问题。原谅我的无知,但是为什么像 GenSet 这样的“辅助”类型会被声明为不变的呢?

我是否遗漏了某些东西(很可能),或者这是否是 Scala 奇妙的集合框架的弱点(我认为不太可能)?

4

1 回答 1

3

这是一个有意识的设计决定,使 Set.contains/apply 类型安全。你不应该有 as:Set[Int] 并且不小心做类似 s.contains("x") 的事情,这总是假的,所以可能不是你想要的。此外,Set[T] 实现了 Function[T,Boolean],这只有在 apply 方法不采用 Any 时才有可能。

在 scala-user 邮件列表上,关于这个话题的讨论没完没了。参见例如thisthis

这是第二次讨论中保罗菲利普斯的引述,很好地总结了基本原理:

“是的,apply(别名为 contains)是 Set 的核心操作,而 contains 确实是 Seq 上的“只是一些方法”。

没有人说你有时不需要协变 Set,但总的来说,它是更有用的不变量。”

请注意,您始终可以通过隐式转换为集合添加协方差。这意味着,如果您有一个 Set[Int] 和一个采用 Set[Any] 的方法,它将起作用。但这也意味着你现在可能不小心调用了 Set[Int].contains("x"),编译器不会捕捉到错误(你总是会得到 false)。

scala> implicit def setIsCovariant[T,U <: T](s:Set[U]):Set[T] = s.asInstanceOf[Set[T]]
setIsCovariant: [T, U <: T](s: Set[U])Set[T]

scala> val s : Set[Int] = Set(1,2,3,4)
s: Set[Int] = Set(1, 2, 3, 4)

scala> s.contains("x")
res0: Boolean = false

scala> val a: Set[Any] = s
a: Set[Any] = Set(1, 2, 3, 4)
于 2013-06-27T22:03:49.187 回答