防止Mixin覆盖等号破坏案例类等式 [英] Prevent Mixin overriding equals from breaking case class equality

查看:193
本文介绍了防止Mixin覆盖等号破坏案例类等式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Squeryl定义了一个特征KeyedEntity,它覆盖了equals,检查if中是否有多个条件,最后调用super.equals.由于superObject,因此它将始终失败.

考虑:

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为假.

是否有一种设计,如果将KeyedEntity混合到案例类中,它将保持案例类的相等性?我尝试将案例类类型的自键入和参数化BetterKeyedEntity[K,P] { self: P => ... }设置为P,但这会导致等于等式的无限递归.

就目前情况而言,superObject,因此KeyedEntity中被覆盖的等号的最后分支将始终返回false.

解决方案

如果存在equals覆盖,通常不会为案例类生成的结构相等性检查似乎不会生成.但是,必须注意一些细微之处.

将基于id的相等性概念混和为结构性相等性可能不是一个好主意,因为我可以想象这可能会导致细微的错误.例如:

  • x: A(1)y: A(1)尚未持久,因此它们相等
  • 然后将它们持久化,并且由于它们是单独的对象,因此持久性框架可能会将它们持久化为单独的实体(我不知道Squeryl,也许在那里不是问题,但这走的路很短)
  • 坚持之后,由于id不同,它们突然变得不相等.

更糟糕的是,如果xy持久化为相同的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])

中那样直接传递typeclass实例来决定要使用哪种相等概念.

请注意,类似地,您可能还需要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

对于EqHashable,同样可以轻松实现.

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 and y: 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覆盖等号破坏案例类等式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆