空列表相等性如何工作?

How does empty List equality work?

提问人:Polymerase 提问时间:7/22/2018 最后编辑:Andrey TyukinPolymerase 更新时间:7/22/2018 访问量:688

问:

运营商真的按内容比较列表吗?特别是关于空列表?==

以下比较按预期工作

List("A", "B", "C") == "ABC".split("").toList // true
List() == List() // true
List.empty[String] == List.empty[String] // true

但是,不同类型的空列表比较会产生令人困惑的结果:

List.empty[String] == List.empty[Int] // true: on different types?

编辑:在最初的问题中,我做了一个误导性的测试用例,安德烈已经澄清了。谢谢。转载于此处

val emptyStrSplit = "".split("").toList // List("") and not List() as displayed in Console
List.empty[String] == emptyStrSplit // false: b/c List() != List("")
Scala 等于 相等 子类型

评论

1赞 Andrey Tyukin 7/22/2018
"".split("").toList莫。它。在你删除空洞的错误假设后,这个问题是否仍然会问任何重要问题?List()List("")"".split("")
1赞 user2357112 7/22/2018
toString笨拙且模棱两可 - in 中的空字符串在打印输出中是不可见的,因此它看起来像 .List("")List()
0赞 Polymerase 7/22/2018
嗨,oups 被 的输出愚弄了,无法检查大小,谢谢。所以这个问题很傻。现在唯一有趣的部分变成了 如何检查两个不同类型的空列表 .将相应地编辑问题List()"".split("").toListList.empty[T1] == List.empty[T2]

答:

5赞 Andrey Tyukin 7/22/2018 #1
  • List.empty[String]是单例对象,它扩展(协变,子类型)。NilList[Nothing]List[String]
  • List.empty[Int]是单例对象,它扩展(协变,子类型)。NilList[Nothing]List[Int]
  • 每个单例对象都与自身相等。
  • 因此,给出了真实。Nil == Nil

因此,从本质上讲,您有一个同时具有类型和类型的对象。如果你有子类型,这就是你得到的。我在这里看不出有什么奇怪或矛盾的地方。NilList[String]List[Int]

如果要确保类型相同,可以使用默认值为 type 的证据,然后检查编译器是否提供了非证据:implicitA =:= Bnullnull

def eqSameType[A, B](a: A, b: B)(implicit ev: A =:= B = null) = 
  if (ev == null) false else a == b

例:

scala> eqSameType(List.empty[Int], List.empty[String])
res4: Boolean = false

评论

0赞 Polymerase 7/22/2018
哦,现在我明白为什么了。感谢您的澄清。但仍然在业务逻辑层面。希望产生尽管使用定制的比较器。请参阅下面的完整答案List.empty[String] == List.empty[Int]false
1赞 Andrey Tyukin 7/22/2018
@Polymerase 不,这不一定是“可取的”。有些人认为,如果 for 和 of different types 根本不进行类型检查,则更可取:多元相等x == yxy
1赞 Polymerase 7/22/2018
甚至更好!还通过了布莱恩的测试用例,同一天有很多好东西:-)。语法叫什么?我想阅读更多的文档。eqSameType(List.empty[List[String]], List.empty[List[Int]]) // falseA =:= B = null
0赞 Andrey Tyukin 7/22/2018
@Polymerase 是二进制中缀类型构造函数(糖 ),是 类型参数的默认值。我认为这些东西被称为“广义类型约束”。=:==:=[A, B]= nullevA =:= B=:=
1赞 Brian McCutchon 7/22/2018 #2

除了 Andrey 的回答之外,即使 (或 ) 确实返回了 的新实例 ,您仍然应该期望由于类型擦除而使各种类型的空列表相等。例如,以 为例,其方法每次都会返回一个 new:List.empty[T]List[T]()ListListBufferemptyListBuffer

import scala.collection.mutable.ListBuffer
ListBuffer.empty[Int] == ListBuffer.empty[String]

如果需要一种方法来检测两个列表何时具有不同的编译时类型,可以使用 TypeTags

import scala.reflect.runtime.universe.{ TypeTag, typeTag }

def equalAndSameType[A: TypeTag, B: TypeTag](as: Seq[A], bs: Seq[B]) =
  typeTag[A] == typeTag[B] && as == bs

equalAndSameType(List.empty[Int], List.empty[String]) // returns false

不过,我不确定这什么时候有用。

评论

0赞 Polymerase 7/22/2018
哦,太酷了,看起来我们在几秒钟内就想出了同样的想法。顺便说一句,您能详细说明一下 vs 的用法吗?TypeTagClassTag
1赞 Brian McCutchon 7/22/2018
@Polymerase 请参阅文档链接。Typetag 具有有关泛型的更多信息,因此基于类标记的解决方案在嵌套更深的泛型上将失败。考虑;这实际上是一个极端情况,你的答案失败了。equalAndSameType(List.empty[List[String]], List.empty[List[Int]])
0赞 Polymerase 7/22/2018 #3

编辑:这种实现使用还不够好。布莱恩的答案更好。ClassTagTypeTag

尽管从Scala的角度来看,Andrey的回答非常有意义。我觉得必须是假的而不是真的更具有商业意义。下面是一个自定义比较器,使用上下文绑定来支持以下内容。不确定这是否是最优雅的方式。List.empty[String] == List.empty[Int]

import scala.reflect.{ClassTag, classTag}

def customCompareLists[T1: ClassTag, T2: ClassTag](l1: List[T1], l2: List[T2]): Boolean = {
  classTag[T1] == classTag[T2] && l1 == l2
}

customCompareLists(List(), List()) // true
customCompareLists(List.empty[Double], List.empty[Double]) // true
customCompareLists(List.empty[String], List.empty[Int]) // false
customCompareLists(List(1,2), List("A")) // false
customCompareLists(List(1,2), List(1,2)) // true

// FAILED on this case
customCompareLists(List.empty[List[String]], List.empty[List[Int]]) // true: INCORRECT

评论

0赞 Brian McCutchon 7/22/2018
我怀疑“它更具有商业意义”取决于您的用例。
4赞 Alexey Romanov 7/22/2018 #4

即使忽略类型擦除问题和“它们实际上是同一个对象”问题,on 的文档也指出:equalsSeq

def equals(that: Any): Boolean

任意序列的 equals 方法。

返回:如果该序列具有与此序列具有相同顺序的相同元素,则返回 true,否则返回 false

这使得所有空序列相等:它们以相同的顺序具有相同的元素,即 none。empty 等于空 、 、 等。ListVectorQueueStream

您可能还对提供类型安全相等的库感兴趣,例如 ScalacticScalaz 等。