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
实例,但不持久化它们,则它们的比较将中断。我通过为同一个类实现Salat
和Squeryl
后端发现了这一点,然后所有Salat
测试都失败了,因为KeyedEntity
中的isPersisted
是false。
是否存在KeyedEntity
在混合到事例类中时将保持事例类相等的设计?我尝试将事例类类型的BetterKeyedEntity[K,P] { self: P => ... }
自类型化和参数化为P,但它会导致equals中的无限递归。
按照目前的情况,super
是Object
,因此KeyedEntity
中被重写的equals的最后一个分支将始终返回false。
如果有equals
覆盖,通常为事例类生成的结构相等性检查似乎不会生成。然而,必须注意一些微妙之处。
将基于id的平等概念重新组合为结构平等可能不是一个好主意,因为我可以想象这可能会导致微妙的错误。例如:
x: A(1)
和和y: A(1)
尚未持久化,因此它们相等- 然后它们被持久化,由于它们是单独的对象,持久化框架可能会将它们作为单独的实体来持久化(我不知道Squeryl,也许这不是问题,但这是一条细线)
- 在坚持之后,由于id不同,它们突然不相等
更糟糕的是,如果x
和y
被持久化为同一个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
类型类。