例如:
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
The various explanations of why things were done this way in Java似乎没有在这里应用那么多,因为Map和Set确实实现了contains
和朋友的类型安全版本。有没有办法在Seq上做一个类型安全的contains
,而不是将它克隆到Set中?
答案 0 :(得分:30)
问题是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
的最小上限(LUB)和Int
(换句话说,最不常见的超类型)。如果你考虑一下,这正是我们所期望的行为。如果您创建包含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!
这里的问题是我们调用contains
方法传递类型为Int
的值。 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
- 类似方法。
答案 1 :(得分:4)
我不确定为什么事情是这样设计的 - 可能是用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._。)
答案 2 :(得分:3)
如果您愿意放弃中缀以支持常规方法调用,定义和导入以下has(...)方法将避免每次需要类型安全“has”测试时创建实例(值得在内循环中,例如:)
def has[T](s: Set[T], t: T) = s.contains(t)
当然,Set [T]可以放宽到具有contains方法的最小特定类型。
答案 3 :(得分:0)
通过提供类型相等的证据,
X = select count(1) from table where QUARTER(`Creation_Date`) = 1 and Customer = 'X' and Status = 'Available';
Y = select count(1) from table where QUARTER(`Creation_Date`) = 1 and Customer = 'X';
Z = X/Y*100