举例来说:
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中?
5条答案
按热度按时间pbgvytdp1#
问题是
Seq
在其类型参数中是协变的。这对它的大部分功能来说都很有意义。作为一个不可变的容器,它真的应该是协变的。不幸的是,当他们必须定义一个接受一些参数化类型的方法时,这确实会碍事。请考虑以下示例:问题是函数的参数类型总是逆变的,而返回类型总是协变的。因此,
apply
方法可以返回类型为A
的值,因为A
与apply
的返回类型是沿着的。但是,contains
* 不能 * 接受A
类型的值,因为它的参数必须是逆变的。这个问题可以用不同的方法来解决。一种选择是简单地使
A
成为不变类型参数。这使得它可以在协变和逆变上下文中使用。然而,这种设计意味着Seq[String]
将 * 不是 *Seq[Any]
的子类型。另一种选择(也是最常用的一种)是使用一个局部类型参数,该参数由协变类型限制。举例来说:这个技巧保留了
Seq[String] <: Seq[Any]
属性,并在编写使用异构容器的代码时提供了一些非常直观的结果。举例来说:本例中
+
函数的结果是Seq[Any]
类型的值,因为Any
是String
和Int
类型的最小上限(LUB)(换句话说,最不常见的超类型)。如果你仔细想想,这正是我们所期望的行为。如果你创建一个同时包含String
和Int
两个组件的序列,那么它的类型 * 应该 * 是Seq[Any]
。不幸的是,这个技巧虽然适用于像
contains
这样的方法,但会产生一些令人惊讶的结果:这里的问题是,我们调用
contains
方法传递了一个Int
类型的值。Scala看到了这一点,并尝试推断B
的类型,它是Int
和A
的超类型,在本例中示例化为String
。这两个类型的LUB是Any
(如前所示),因此contains
的本地类型示例化将是Any => Boolean
。因此,contains
方法 * 看起来 * 不是类型安全的。这个结果对于
Map
或Set
来说不是问题,因为它们的参数类型都不是协变的:所以,长话短说,协变容器类型上的
contains
方法不能被限制为只接受组件类型的值,因为函数类型的工作方式(参数类型的逆变)。这并不是Scala的限制或糟糕的实现,这是一个数学事实。安慰奖是,这在实践中真的不是一个问题。而且,正如其他答案所提到的,如果你真的需要额外的检查,你可以定义自己的隐式转换,添加一个“类型安全”的
contains
类方法。flseospp2#
我不知道为什么要这样设计--可能是为了反映Java中的某些东西。
无论如何,使用pimp-my-library模式比克隆到一个集合中更有效:
(You我可能想把这些东西和其他方便的东西放在一个对象中,然后在大多数源文件中导入MyHandyObject._。)
hujrc8aj3#
如果你愿意放弃中缀而支持常规的方法调用,定义并导入下面的has(...)方法将避免每次需要类型安全的“has”测试时都创建一个示例(值得在内部循环中使用,例如):
当然,Set[T]可以放宽到具有contains方法的最不特定的类型。
fbcarpbf4#
通过提供类型相等的证据,
juzqafwq5#
在回答关于类型安全的
contains
的第二个问题时,如果您正在使用cats
,则contains_
在UnorderedFoldables上可用: