Scala 空列表等式是如何工作的?

Scala 空列表等式是如何工作的?,scala,types,equals,equality,subtyping,Scala,Types,Equals,Equality,Subtyping,操作员是否真的按内容比较列表?特别是关于空列表 以下比较工作与预期一样 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

操作员是否真的按内容比较列表?特别是关于空列表

以下比较工作与预期一样

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?
编辑:在最初的问题中,我做了一个误导性的测试用例,Andrey对此进行了澄清。谢谢复制于此

val emptyStrSplit = "".split("").toList // List("") and not List() as displayed in Console
List.empty[String] == emptyStrSplit // false: b/c List() != List("")
  • List.empty[String]
    是单例对象
    Nil
    ,它扩展了
    List[Nothing]
    (协变地,是
    List[String]
    的子类型)
  • List.empty[Int]
    是单例对象
    Nil
    ,它扩展了
    List[Nothing]
    (协变地,是
    List[Int]
    的子类型)
  • 每个单例对象都等于它自己
  • 因此,
    Nil==Nil
    给出true
因此,本质上,您有一个对象
Nil
,它同时是
List[String]
类型和
List[Int]
类型。这就是你得到的,如果你有子类型。我看不出有什么奇怪或矛盾的地方

如果要确保类型相同,可以使用默认值
null
implicit
类型证据
A=:=B
,然后检查编译器是否提供了非
null
证据:

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

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

import scala.collection.mutable.ListBuffer
ListBuffer.empty[Int] == ListBuffer.empty[String]
如果希望找到一种方法来检测两个列表何时具有不同的编译时类型,可以使用:


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

编辑:使用
ClassTag
的这个实现还不够好。Brian使用
TypeTag
的答案更好

虽然从Scala的角度来看,Andrey的回答很有道理。我觉得将
List.empty[String]==List.empty[Int]
设置为false而不是true更符合商业意义。下面是一个自定义比较器,它使用上下文绑定来支持以下内容。不知道这是不是最优雅的方式

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

即使忽略类型擦除问题和“它们实际上是同一个对象”问题,
equals
on
Seq
的文档说明:

def等于(即:任意):布尔值

任意序列的equals方法

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

这使得所有空序列相等:它们具有相同顺序的相同元素,即无。空的
列表
等于空的
向量
队列


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

“”.split(“”)。toList
不是
List()
。它是
列表(“”)
。在你消除了错误的假设后,这个问题还有什么重要的问题吗?
拆分(“”
是空的?
toString
很难理解,因为
列表(“”
中的空字符串在打印输出中是不可见的,所以看起来像
列表()
。嗨,输出
列表()愚弄了oups
的。拆分(“”)。toList
无法检查大小,谢谢。所以这个问题相当愚蠢。现在唯一有趣的部分是如何检查两个不同类型的空列表
List.empty[T1]==List.empty[T2]
。现在我明白为什么了。谢谢你的澄清。但仍处于业务逻辑级别。希望
List.empty[String]==List.empty[Int]
生成
false
,尽管使用定制的比较器。见完整答案below@Polymerase不,这不一定是“可取的”。一些人认为,如果不同类型的
x
y
根本不进行打字检查,则更可取:。甚至更好!还通过了Brian的测试用例
eqSameType(List.empty[List[String]],List.empty[List[Int]]//false
同一天有这么多好事:-)。调用的语法是什么?我想进一步阅读文档。@Polymerase
=:=
是一个二进制中缀类型的构造函数(sugar for
=:=[a,B]
),
=null
a=:=B
类型的参数
ev
的默认值。我认为
=:=
东西被称为“广义类型约束”。哦,酷,看起来我们在几秒钟内就想出了相同的想法。顺便说一句,你能详细说明一下
TypeTag
vs
ClassTag
的用法吗?请参见文档链接。TypeTag有更多关于泛型的信息,因此基于classtag的解决方案将在嵌套更深入的泛型上失败。考虑<代码>均衡器(列表):空[列表[Str] ],列表,空[列表[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