Scala 为什么Seq.contains接受类型Any而不是类型参数A?
例如:Scala 为什么Seq.contains接受类型Any而不是类型参数A?,scala,Scala,例如: 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 trait Seq[+A] { def +[B >: A](v: B): Seq[B] } val s: Seq[String] = ... s + 1
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
trait Seq[+A] {
def +[B >: A](v: B): Seq[B]
}
val s: Seq[String] = ...
s + 1 // will be of type Seq[Any]
在这里似乎应用不多,因为Map和Set实现了
包含的和朋友的类型安全版本。除了将其克隆到一个集合之外,还有什么方法可以在一个序列上实现类型安全的包含?我不知道为什么事情会这样设计——可能是为了在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
class-SeqWithHas[T](s:Seq[T]){
def具有(t:t)=s.包含(t)
}
隐式定义seq2seqwithhas[T](s:Seq[T])=新的SeqWithHas
scala>列表(“3”、“5”)有1个
:7:错误:类型不匹配;
发现:Int(1)
必需:java.lang.String
列表(“3”、“5”)有1个
^
scala>列表(“3”、“5”)有“1”
res1:Boolean=false
(您可能希望将这些内容和其他方便的内容放在一个对象中,然后在大多数源文件中导入MyHandyObject。)如果您愿意放弃中缀而使用常规方法调用,那么定义和导入以下has(…)方法将避免每次需要类型安全时创建实例“has”测试(例如,在内部循环中是值得的):
自然,Set[T]可以放宽到包含方法的最不特定的类型。问题是Seq
的类型参数是协变的。这对于它的大多数功能来说很有意义。作为一个不可变的容器,它确实应该是协变的。不幸的是,这确实会妨碍他们定义方法它考虑了一些参数化的类型。
trait Seq[+A] {
def apply(i: Int): A // perfectly valid
def contains(v: A): Boolean // does not compile!
}
问题是函数的参数类型总是逆变的,返回类型总是协变的。因此,apply
方法可以返回a
类型的值,因为a
与apply
的返回类型是协变的。但是,contains
不能接受类型的值de>A
,因为它的参数必须是逆变的
这个问题可以用不同的方法解决。一个选项是简单地将A
作为一个不变类型参数。这允许它在协变和逆变上下文中使用。但是,这种设计意味着Seq[String]
将不是Seq[Any]
的子类型。另一个选项是(也是最常用的一种)是使用一个局部类型参数,该参数下面以协变类型为界。例如:
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
trait Seq[+A] {
def +[B >: A](v: B): Seq[B]
}
val s: Seq[String] = ...
s + 1 // will be of type Seq[Any]
此技巧保留了Seq[String]布尔值
。因此,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的限制或糟糕的实现,而是一个数学事实
值得安慰的是,这在实践中并不是一个问题。而且,正如其他答案所提到的,如果您确实需要额外的检查,您总是可以定义自己的隐式转换,添加一个“类型安全”的包含类似于的方法。通过提供类型相等的证据
def contains[A,B](xs: List[A], x: B)(implicit ev: A =:= B) = xs.contains(x)
此注释字段太小,无法发布代码,但优化非常好地允许中缀方法只导致一点速度减慢:隐式与方法(长度为3的列表上有2M个测试):1247对1141;1215对1076;1184对1091;1181对1087;1180对1085;1183对1095;1200对1103;1203对1093;1183对1088;1188对1087。还不错——隐式对象创建的开销约为10%。遗憾的是,没有一种机制来指导编译器关于那些仅在形式上满足类型要求的参数可以证明列表[B]只能包含B,但列表[B]也可以被重新解释为列表[a],其中B就是这里发生的事情。Seq[String]
被重新解释为Seq[Any]
,而且由于Int,我很想看到这成为数学事实的数学(指数函子是反变的除外)。令人欣慰的是,这在实践中并不是一个问题:一个问题是,用户可能会混淆包含和存在。执行列表(1,2)时不会出现编译器错误。包含((x:Int)=>x==2)
,只会得到false
。