有选择地禁用Scala中的包含? (正确输入List.contains)

时间:2011-12-02 17:47:09

标签: list scala contains covariance implicit-cast

List("a").contains(5)

由于Int永远不会包含在String列表中,因此应该会在编译时产生错误,但事实并非如此。

浪费并静默地测试列表中包含的每个String的{​​{1}}的相等性,这是永远不可能的(5在Scala中永远不等于"5"。< / p>

这被命名为&#34; the 'contains' problem&#34;。并且some have implied如果类型系统无法正确键入这样的语义,那么为什么需要额外的努力来强制执行类型。所以我认为这是一个需要解决的重要问题。

5的类型参数化B >: A输入任何类型为List.contains的超类型(列表中包含的元素类型)。

A

此类型参数化是必要的,因为trait List[+A] { def contains[B >: A](x: B): Boolean } 声明列表在+A类型上为covariant,因此A无法在逆变中使用position,即作为输入参数的类型。协变列表(必须是不可变的)是much more powerful for extension而不是不变列表(可以是可变的)。

A在上面有问题的示例中是A,但String不是Int的超类型,所以发生了什么? Scala中的implicit subsumption决定StringAnyString的共同超类型。

Scala的创建者,Martin Odersky,suggested,修复方法是将输入类型Int限制为只有那些具有B不能使用的方法的类型Any。有。。

trait List[+A] {
   def contains[B >: A : Eq](x: B): Boolean
}

但是这并没有解决问题,因为两种类型(其中输入类型不是列表元素类型的超类型)可能具有相互超类型,它是Any的子类型,即也是Eq的子类型。因此,它将编译时没有错误,并且将保留错误键入的语义。

禁用每个not an ideal solution的隐式包含,因为隐式包含是下面的包含到Any的示例的原因。当接收站点(例如作为函数参数传递)为相互超类型(甚至可能不是Any)正确键入语义时,我们不希望被迫使用类型转换。

trait List[+A] {
   def ::[B >: A](x: B): List[B]
}

val x : List[Any] = List("a", 5) // see[1]

[1] List.apply calls the :: operator

所以我的问题是这个问题的最佳方法是什么?

我的初步结论是,应该在定义站点关闭隐式包含,否则语义不能正确输入。我将提供一个答案,显示如何在方法定义站点关闭隐式包含。有替代解决方案吗?

请注意,此问题是一般性的,不会与列表隔离。

更新:我有filed an improvement request并开始scala discussion thread on this。我还在Kim Stebel和Peter Schmitz的回答中添加了评论,表明他们的答案有错误的功能。因此没有解决方案。同样在前面提到的讨论主题中,我解释了为什么我认为soc的答案是不正确的。

6 个答案:

答案 0 :(得分:12)

这在理论上听起来不错,但在我看来在现实生活中分崩离析。

equals不是基于类型而且contains正在构建之上。

这就是为什么像1 == BigInt(1)这样的代码可以工作并返回大多数人期望的结果的原因。

在我看来,让containsequals更严格是没有意义的。

如果contains更严格,List[BigInt](1,2,3) contains 1之类的代码将完全停止运作。

顺便说一下,我认为“不安全”或“不安全”是正确的用语。

答案 1 :(得分:8)

为什么不使用相等的类型类?

scala> val l = List(1,2,3)
l: List[Int] = List(1, 2, 3)

scala> class EQ[A](a1:A) { def ===(a2:A) = a1 == a2 } 
defined class EQ

scala> implicit def toEQ[A](a1:A) = new EQ(a1)
toEQ: [A](a1: A)EQ[A]

scala> l exists (1===)
res7: Boolean = true

scala> l exists ("1"===)
<console>:14: error: type mismatch;
 found   : java.lang.String => Boolean
 required: Int => Boolean
              l exists ("1"===)
                           ^

scala> List("1","2")
res9: List[java.lang.String] = List(1, 2)

scala> res9 exists (1===)
<console>:14: error: type mismatch;
 found   : Int => Boolean
 required: java.lang.String => Boolean
              res9 exists (1===)

答案 2 :(得分:4)

我认为你误解了马丁的解决方案,它不是B <: Eq,而是B : Eq,这是

的捷径
def Contains[B >: A](x: B)(implicit ev: Eq[B])

然后Eq[X]将包含方法

def areEqual(a: X, b: X): Boolean

这与在层次结构中移动任何稍低的equals方法不同,这确实不会解决在Any中使用它的问题。

答案 3 :(得分:3)

在我的图书馆扩展程序中,我使用:

class TypesafeEquals[A](val a: A) {
  def =*=(x: A): Boolean = a == x
  def =!=(x: A): Boolean = a != x
}
implicit def any2TypesafeEquals[A](a: A) = new TypesafeEquals(a)


class RichSeq[A](val seq: Seq[A]) { 
  ...
  def containsSafely(a: A): Boolean = seq exists (a =*=)
  ...
}
implicit def seq2RichSeq[A](s: Seq[A]) = new RichSeq(s)

所以我避免打电话给contains

答案 4 :(得分:3)

示例使用L代替ListSeqLike,因为要将此解决方案应用于这些集合的预先存在的contains方法,需要更改为预先存在的库代码。其中一个目标是实现平等的最佳方式,而不是与当前库交互的最佳方法(尽管需要考虑向后兼容性)。另外,我的另一个目标是这个答案通常适用于任何想要因任何原因选择性地禁用Scala编译器的隐式包含特性的方法函数,而不一定与等式语义相关联。

case class L[+A]( elem: A )
{
   def contains[B](x: B)(implicit ev: A <:< B) = elem == x
}

上面根据需要生成错误,假设List.contains所需的语义是输入应该等于和包含元素的超类型

L("a").contains(5)
error: could not find implicit value for parameter ev: <:<[java.lang.String,Int]
       L("a").contains(5)
                      ^

当不需要隐式包含时,不会生成错误。

scala> L("a").contains(5 : Any)
defined class L

scala> L("a").contains("")
defined class L

这会通过要求输入参数类型B与作为输入传递的参数类型相同来禁用隐式包含(有选择地在方法定义站点)(即不能隐式地使用A进行包含),然后单独require implicit evidence表示B是一个,或者具有隐式可替换的超级类型A。]


更新2012年5月3日:上面的代码未完成,如下所示,关闭方法定义网站上的所有包含操作无法提供所需的结果。

class Super
defined class Super
class Sub extends Super
defined class Sub
L(new Sub).contains(new Super)
defined class L
L(new Super).contains(new Sub)
error: could not find implicit value for parameter ev: <:<[Super,Sub]
       L(new Super).contains(new Sub)
                            ^

获得所需形式的包含的唯一方法是在方法(调用)使用站点进行投射。

L(new Sub).contains(new Super : Sub)
error: type mismatch;
 found   : Super
 required: Sub
       L(new Sub).contains(new Super : Sub)
                           ^
L(new Super).contains(new Sub : Super)
defined class L

Per soc's answerList.contains的当前语义是输入应该等于,但不一定是包含元素的超类型。这假定List.contains承诺任何匹配的项仅等于且不需要是输入实例的(子类型或)副本。当前的通用等式接口Any.equals : Any => Boolean是统一的,因此相等不会强制执行子类型关系。如果这是List.contains所需的语义,则不能使用子类型关系来优化编译时语义,例如:禁用隐式包含,我们陷入潜在的语义效率低下,这会降低List.contains的运行时性能。

虽然我将研究和思考更多关于平等和包含,但是afaics我的答案对于在方法定义网站选择性地禁用隐式包含的一般目的仍然有效。

我的思维过程也在全面进行。最好的平等模式。


更新:我在soc's answer下面添加了评论,所以我现在认为他的观点不相关。平等应该始终基于一种亚型关系,其中就是马丁奥德斯基is proposing对于新的平等改革(参见他的contains BitInt(1) == 1)。任何ad-hoc多态等价(例如contains)都可以使用隐式转换来处理。我在下面version的评论中解释说,如果没有我的改进,马丁提出的Any将会出现语义错误,因此相互隐式包含的超类型(Eq除外)将选择错误Eq.eq的隐式实例(如果存在,则不必要的编译器错误)。我的解决方案禁用此方法的隐式包含,这是trait Eq[A] { def eq(x: A, y: A) = x == y } implicit object EqInt extends Eq[Int] implicit object EqString extends Eq[String] case class L[+A]( elem: A ) { def contains[B](x: B)(implicit ev: A <:< B, eq: Eq[B]) = eq.eq(x, elem) } L("a").contains("") 的子类型参数的正确语义。

Eq.eq

注意implicit object可以选择由L("a").contains(5 : Any)替换(不会被覆盖,因为没有虚拟继承,见下文)。

请注意,根据需要,Any.equals不再编译,因为不再使用case class L[+A]( elem: A ) { def contains[B : Eq](x: B)(implicit ev: A <:< B) = eq.eq(x, elem) }

我们可以缩写。

x == y

添加x.==必须是虚拟继承调用,即override应声明Eq,因为A中有A Eq.eg类型类。类型参数implicit object是不变的(因为trait在逆变位置中用作Any.equals的输入参数。然后在界面上didierd's answer {{1}}(a.k.a。{{1}})。

因此,{{1}}覆盖仍然必须检查输入的具体类型是否匹配。编译器无法消除这种开销。

答案 5 :(得分:2)

我认为我至少有一些合法的解决方案可以解决此处发布的问题 - 我的意思是List("1").contains(1)的问题: https://docs.google.com/document/d/1sC42GKY7WvztXzgWPGDqFukZ0smZFmNnQksD_lJzm20/edit