问题描述
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
为假.
是否有一种设计,如果将KeyedEntity
混合到案例类中,它将保持案例类的相等性?我尝试将案例类类型的自键入和参数化BetterKeyedEntity[K,P] { self: P => ... }
设置为P,但这会导致等于等式的无限递归.
就目前情况而言,super
是Object
,因此KeyedEntity
中被覆盖的等号的最后分支将始终返回false.
如果存在equals
覆盖,通常不会为案例类生成的结构相等性检查似乎不会生成.但是,必须注意一些细微之处.
将基于id的相等性概念混和为结构性相等性可能不是一个好主意,因为我可以想象这可能会导致细微的错误.例如:
-
x: A(1)
和y: A(1)
尚未持久,因此它们相等 - 然后将它们持久化,并且由于它们是单独的对象,因此持久性框架可能会将它们持久化为单独的实体(我不知道Squeryl,也许在那里不是问题,但这走的路很短)
- 坚持之后,由于id不同,它们突然变得不相等.
更糟糕的是,如果x
和y
持久化为相同的ID,则hashCode
在持久化之前和之后都会有所不同(来源显示,如果持久保存,则为ID的hashCode).这会破坏不变性,并会导致非常糟糕的行为(例如,放置在地图中).参见我要证明断言失败的要点.
因此,请勿隐式地将结构和基于id的相等性混在一起.另请参见 Hibernate的上下文中解释的内容.
类型类
必须指出的是,其他人指出(需要参考)基于方法的平等的概念是有缺陷的,因为这样的原因(不仅只有一种方法可以使两件事相等).因此,您可以定义一个描述平等的类型类:
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])
请注意,类似地,您可能还需要Hashable
类型类.
自动生成Eq实例
这种情况与Scala stdlib的scala.math.Ordering
类型类非常相似.这是使用出色的Ordering实例的示例="https://www.google.hu/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCQQFjAA&url=https://github.com/milessabin/shapeless&ei=SSxUUMuhOsXtsgbr24HABA&usg=AFQjCNFvx3dfQCrdoYO7rK_GCYBDLKxxQA&sig2=OXRlsO9QlRRkEB5Heo3hcA"="> nofollow norefer
对于Eq
和Hashable
,同样可以轻松实现.
Scalaz
请注意, scalaz具有Equal
typeclass ,具有不错的皮条客模式,您可以使用它们编写x === y
而不是eqInstance.equal(x, y)
.我还不知道它具有Hashable
类型类.
Squeryl
defines a trait KeyedEntity
which overrides equals
, checking for several conditions in an if and calling super.equals
in the end. Since super
is Object
, it will always fail.
Consider:
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
Thus, if you declare
case class Record(id: Long, name: String ...) extends KeyedEntity[Long] { ... }
-- and you create several Record
instances but do not persist them, their comparison will break. I found this by implementing both Salat
and Squeryl
back ends for the same class, and then all Salat
tests fail since isPersisted
from KeyedEntity
is false.
Is there a design whereby KeyedEntity
will preserve case class equality if mixed into a case class? I tried self-typing and parameterizing BetterKeyedEntity[K,P] { self: P => ... }
for the case class type as P but it causes infinite recursion in equals.
As things stand right now, super
is Object
so the final branch of the overridden equals in KeyedEntity
will always return false.
The structural equality check usually generated for case classes seems not to be generated if there is an equals
override. However some subtleties have to be noted.
Mixing the concept of id-based equality falling back to structural equality might not be a good idea, since I can imagine that it may lead to subtle bugs. For example:
x: A(1)
and andy: A(1)
are not yet persisted, so they are equal- then they get persisted, and since they are separate objects, the persistence framework may persist them as separate entities (I don't know Squeryl, maybe not an issue there, but this is a thin line to walk)
- after persisting, they are suddenly not equal since the id differs.
Even worse, if x
and y
get persisted to the same id, the hashCode
will differ before and after persisting (the source shows that if persisted it is the hashCode of the id). This breaks immutability, and will lead to very bad behavior (when put in maps for example). See this gist in which I demonstrate the assert failing.
So don't mix structural and id-based equality implicitly. Also see this explained in the context of Hibernate.
Typeclasses
It have to be noted that others pointed out (ref needed) that the concept of method-based equality is flawed, for such reasons (there is not only one way two thing can be equal). Therefore you can define a typeclass which describes Equality:
trait Eq[A] {
def equal(x: A, y: A): Boolean
}
and define (possibly multiple) instances of that typeclass for your classes:
// 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
}
then you can request that things are members of the Eq typeclass:
def useSomeObjects[A](a: A, b: A)(implicit aEq: Eq[A]) = {
... aEq.equal(a, b) ...
}
So you can decide which notion of equality to use by importing the appropriate typeclass in scope, or passing the typeclass instance directly as in useSomeObjects(x, y)(idEq[Int, SomeClass])
Note that you might also need a Hashable
typeclass, similarly.
Autogenerating Eq instances
This situation is pretty similar to the Scala stdlib's scala.math.Ordering
typeclass. Here is an example for auto-deriving structural Ordering
instances for case classes using the excellent shapeless library.
The same would easy to be done for Eq
and Hashable
.
Scalaz
Note that scalaz has Equal
typeclass, with nice pimp patterns with which you can write x === y
instead of eqInstance.equal(x, y)
. I'm not aware it has Hashable
typeclass, yet.
这篇关于防止Mixin覆盖等号破坏案例类等式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!