例如:
scala> val l:List[String] = List("one", "two")
l: List[String] = List(one, two)
scala> l.contains(1) //wish this didn't compile
res11: Boolean = false 由于Map和Set实现了类型安全版本的contains和friends,因此The various explanations of why things were done this way in Java在这里的应用似乎不多。除了将其克隆到Set中之外,还有什么方法可以在Seq上执行类型安全的contains呢?
发布于 2010-01-17 03:13:19
问题是Seq的类型参数是协变的。这对于它的大部分功能来说是很有意义的。作为一个不可变的容器,它真的应该是协变的。不幸的是,当他们必须定义一个接受一些参数化类型的方法时,这确实会妨碍他们。考虑以下示例:
trait Seq[+A] {
def apply(i: Int): A // perfectly valid
def contains(v: A): Boolean // does not compile!
}问题是函数的参数类型总是逆变的,返回类型总是协变的。因此,apply方法可以返回A类型的值,因为A与apply的返回类型是协变的。但是,contains不能接受A类型的值,因为它的参数必须是逆变量。
这个问题可以用不同的方式解决。一种选择是简单地将A设置为不变类型参数。这使得它既可以用于协变上下文,也可以用于逆变上下文。但是,这种设计意味着Seq[String]不会是Seq[Any]的子类型。另一种选择(也是最常用的一种)是使用局部类型参数,该参数在下面由协变类型限定。例如:
trait Seq[+A] {
def +[B >: A](v: B): Seq[B]
}这个技巧保留了Seq[String] <: Seq[Any]属性,并在编写使用异构容器的代码时提供了一些非常直观的结果。例如:
val s: Seq[String] = ...
s + 1 // will be of type Seq[Any]本例中的+函数的结果是一个Seq[Any]类型的值,因为Any是String和Int类型(换句话说,最不常见的超类型)的最小上界(LUB)。如果你仔细想想,这正是我们所期望的行为。如果创建的序列同时包含String和Int组件,则其类型应为Seq[Any]。
不幸的是,这个技巧,虽然适用于像contains这样的方法,但产生了一些令人惊讶的结果:
trait Seq[+A] {
def contains[B >: A](v: B): Boolean // compiles just fine
}
val s: Seq[String] = ...
s contains 1 // compiles!这里的问题是,我们调用的是传递Int类型的值的contains方法。Scala看到了这一点,并尝试推断B的类型,它是Int和A的超类型,在本例中被实例化为String。这两种类型的LUB是Any (如前所述),因此contains的本地类型实例化将是Any => Boolean。因此,contains方法似乎不是类型安全的。
这个结果对于Map或Set来说不是问题,因为它们的参数类型都不是协变的:
trait Map[K, +V] {
def contains(key: K): Boolean // compiles
}
trait Set[A] {
def contains(v: A): Boolean // also compiles
}因此,长话短说,协变容器类型上的contains方法不能被限制为只接受组件类型的值,因为函数类型的工作方式(参数类型中的逆变)。这并不是Scala的局限性或糟糕的实现,而是一个数学事实。
值得安慰的是,这在实践中真的不是问题。而且,正如其他答案所提到的,如果您确实需要额外的检查,您可以定义自己的隐式转换,它添加了一个“类型安全”的contains-like方法。
发布于 2010-01-17 02:10:20
我不确定为什么要这样设计--可能是为了反映Java中的某些东西。
无论如何,使用pimp-my-library模式比克隆到一个集合中更有效:
class SeqWithHas[T](s: Seq[T]) {
def has(t: T) = s.contains(t)
}
implicit def seq2seqwithhas[T](s: Seq[T]) = new SeqWithHas(s)
scala> List("3","5") has 1
<console>:7: error: type mismatch;
found : Int(1)
required: java.lang.String
List("3","5") has 1
^
scala> List("3","5") has "1"
res1: Boolean = false(您可能希望将这些内容和其他方便的内容放入单个对象中,然后在大多数源文件中导入MyHandyObject._。)
发布于 2010-01-17 02:38:46
如果您愿意放弃infix,转而使用常规的方法调用,那么定义并导入下面的has(...)方法将避免每次需要类型安全的"has“测试时创建一个实例(例如,值得在内部循环中使用):
def has[T](s: Set[T], t: T) = s.contains(t)自然,SetT可以简化为具有contains方法的最不具体的类型。
https://stackoverflow.com/questions/2078246
复制相似问题