防止Mixin重写equals破坏大小写类相等



Squeryl定义了一个特征KeyedEntity,它覆盖equals,检查if中的几个条件,并最终调用super.equals。由于super就是Object,所以它总是会失败。

考虑:

trait T { override def equals(z: Any):Boolean = super.equals(z)} }
case class A(a: Int) extends T
val a = A(1); val b = A(1)
a==b // false

因此,如果您申报

case class Record(id: Long, name: String ...) extends KeyedEntity[Long] { ... }

--如果您创建了几个Record实例,但不持久化它们,则它们的比较将中断。我通过为同一个类实现SalatSqueryl后端发现了这一点,然后所有Salat测试都失败了,因为KeyedEntity中的isPersisted是false。

是否存在KeyedEntity在混合到事例类中时将保持事例类相等的设计?我尝试将事例类类型的BetterKeyedEntity[K,P] { self: P => ... }自类型化和参数化为P,但它会导致equals中的无限递归。

按照目前的情况,superObject,因此KeyedEntity中被重写的equals的最后一个分支将始终返回false。

如果有equals覆盖,通常为事例类生成的结构相等性检查似乎不会生成。然而,必须注意一些微妙之处。

将基于id的平等概念重新组合为结构平等可能不是一个好主意,因为我可以想象这可能会导致微妙的错误。例如:

  • x: A(1)和和y: A(1)尚未持久化,因此它们相等
  • 然后它们被持久化,由于它们是单独的对象,持久化框架可能会将它们作为单独的实体来持久化(我不知道Squeryl,也许这不是问题,但这是一条细线)
  • 在坚持之后,由于id不同,它们突然不相等

更糟糕的是,如果xy被持久化为同一个id,则hashCode在持久化之前和之后会有所不同(源代码显示,如果持久化,则是id的hashCode)。这破坏了不变性,并将导致非常糟糕的行为(例如,当放在地图中时)。请参阅我演示断言失败的要点。

因此,不要隐式地混合结构和基于id的平等。另请参阅Hibernate上下文中的解释。

类型类别

必须注意的是,其他人指出(需要参考)基于方法的平等概念是有缺陷的,原因如下(并非只有一种方法可以使两者平等)。因此,您可以定义一个描述Equality:的类型类

trait Eq[A] {
def equal(x: A, y: A): Boolean
}

并为您的类定义(可能有多个)该类型类的实例:

// structural equality
implicit object MyClassEqual extends Eq[MyClass] { ... }
// id based equality
def idEq[K, A <: KeyedEntity[K]]: Eq[A] = new Eq[A] {
def equal(x: A, y: A) = x.id == y.id
}

然后你可以请求事物是Eq类型类的成员:

def useSomeObjects[A](a: A, b: A)(implicit aEq: Eq[A]) = {
... aEq.equal(a, b) ...
}

因此,您可以通过在作用域中导入适当的typeclass,或者像useSomeObjects(x, y)(idEq[Int, SomeClass])中那样直接传递typeclass实例,来决定使用哪个相等概念

请注意,类似地,您可能还需要一个Hashable类型类。

自动生成Eq实例

这种情况与Scala stdlib的scala.math.Ordering类型类非常相似。这里是一个使用优秀的shapeless库为case类自动派生结构Ordering实例的示例。

对于CCD_ 28和CCD_。

标量

请注意,scalaz具有Equal类型类,具有漂亮的皮条模式,您可以用它来编写x === y而不是eqInstance.equal(x, y)。我还不知道它有Hashable类型类。

最新更新