【问题标题】:Scala case classes in collections集合中的 Scala 案例类
【发布时间】:2009-12-16 21:25:30
【问题描述】:

场景:例如,我正在解析一个 IL 并希望从基于堆栈的表示转换为 CFG。

我的 IL 由多个操作组成,例如 PushInt(value)、Pop 等。现在的问题是哪种实现在 Scala 方面是正确的。我很想使用案例类/对象或提取器,这样我就可以编写代码了

op match {
  case PushInt(x) => doSomethingWith x
  case Pop => ...
}

现在问题存在于类似PushInt(1) :: PushInt(1) :: Pop :: Pop 的序列中,因为 PushInt(1) 等于 PushInt(1) 并且我不能将多个(相等的)操作添加到集合中。但是我知道我正在丢弃一些信息,即流中的位置,但这会隐式存储为序列中的 te 索引。

  • 一种可能的解决方案是重写 hashCode 方法并打破 equal/hashCode 的规则。我对此并不满意。

  • 另一种选择是在抽象库中存储一个“创建时间”计数器,以便case class PushInt(value: Int) extends AbstractOp(AbstractOp.nextIndex)

  • 使用提取器,但在这种情况下,我会错过一些不错的功能,例如 hashCode、equals、toString 的实现,以及更重要的是检查穷举匹配。

所以我现在的问题是如何根据我的要求为我的结构建模。就 Scala 而言,任何可能的解决方案是否“正确”?

【问题讨论】:

  • 不能在一个集合中添加多个相等的操作?不是只有当集合是 Set 时才会这样吗?
  • 不行,在序列中求indexOf就更惨了。如果对象相等,这当然是正确的。
  • List 中当然可以有重复的相同元素。
  • 使用显式定义的提取器与使用案例类与编译器对穷举匹配的检查是正交的。这取决于要匹配的实例类型定义中的密封关键字。
  • Joa,这个问题太模糊了,假设太多。什么是 IL?为什么不能在集合中添加多个相等的操作?你到底想做什么?坦率地说,您的要求没有说明。

标签: scala


【解决方案1】:

首先,让我们解决找到您想要的确切实例的问题:

scala> trait AbstractOp
defined trait AbstractOp

scala> case class Pop() extends AbstractOp {
     |   override def equals(other: Any) = other match {
     |     case that: Pop => this eq that
     |     case _ => false
     |   }
     | }
defined class Pop

scala> case class PushInt(val i: Int) extends AbstractOp {
     |   override def equals(other: Any) = other match {
     |     case that: PushInt => this eq that
     |     case _ => false
     |   }
     | }
defined class PushInt

scala> val l = List(PushInt(1), PushInt(1), Pop(), Pop())
l: List[Product with AbstractOp] = List(PushInt(1), PushInt(1), Pop(), Pop())

scala> val op = l(1)
op: Product with AbstractOp = PushInt(1)

scala> println( l.indexOf( op ) )
1

这当然是指PushInt(1) != PushInt(1),除非它与PushInt(1) 完全相同。它不会破坏equals/hashCode 合同,因为a.equals(b) => a.hashCode == b.hashCode,但a.hashCode == b.hashCode 并不意味着什么。但是,如果您唯一的用途是查找该实例,请尝试以下操作:

scala> case class Pop() extends AbstractOp
defined class Pop

scala> case class PushInt(val i: Int) extends AbstractOp
defined class PushInt

scala> val l = List(PushInt(1), PushInt(1), Pop(), Pop())
l: List[Product with AbstractOp] = List(PushInt(1), PushInt(1), Pop(), Pop())

scala> val op = l(1)
op: Product with AbstractOp = PushInt(1)

scala> println( l.findIndexOf( op eq _ ) )
1

无论哪种方式,如果您将该实例重新插入列表中,您就会遇到麻烦。您必须确保您插入的每个实例都是唯一的。您甚至可以编写自己的集合,或者在插入重复实例时抛出异常,或者复制传递给它的任何实例(使用案例类和 Scala 2.8 上的 copy 方法很容易)。

【讨论】:

  • Daniel,如果我使用您的第二个建议,映射问题呢?我认为它不起作用,所以我应该覆盖equals,对吧?
  • 什么映射问题? (Patrick 读) 哦,对了。是的,就是这样。在这种情况下,您可以覆盖 equals 并为您想要的时间创建一个备用比较运算符 Push(1) == Push(1)。例如,=~= 之类的东西。
  • 对于第一个解决方案,请注意您不需要检查类型,因为您将在之后检查身份。因此有足够的: override def equals(other: Any) = this eq other
  • @Blaisorblade 你试过了吗?这是类型不匹配,因为eq 需要AnyRef,但equals 是在Any 之上定义的。
  • 不,你是对的,我已经习惯了非 Scala,在这种情况下,要做到这一点并不难。无论如何,您仍然可以编写以下代码: case class PushInt(val i: Int) { override def equals(other: Any) = other.asInstanceOf[AnyRef] eq this } 更短并且避免生成任何运行时强制转换。在运行时,AnyValue 的实例已经被装箱了,所以上面的转换只是为了让类型检查器满意,仅此而已。
【解决方案2】:

如果 Joa 不介意 ;) 想象一下这样的代码:

trait AbstractOp
case class Pop() extends AbstractOp
case class PushInt(val i:Int) extends AbstractOp

现在我们构造一个表示程序指令序列的列表

val l=List(PushInt(1), PushInt(1), Pop(), Pop())

第一个问题:你想得到一个操作的索引

val op=l(1) // get the second operation for example
// now you want to get back the index for the op you are using
println( l.indexOf( op1 ) ) // you will get 0 and not 1

第二个问题:你想将前一个列表中的每个操作映射到一个值,这会失败,因为equals不会区分两个Pop,或者两个PushInt。

附: 当然这不是一个答案,我还没有找到如何在其他 cmets 下发布这个 随意将其移动到正确的位置

【讨论】:

  • 你不能把 cmets 做得很大,你也不能在其中格式化代码。在这种情况下,你做了最好的事情,既然你在帮助澄清,我怀疑有人会介意。
  • 我将第二个问题改写为 equals()。在 HahsMap 中插入具有相同哈希码的不同对象只是一个性能问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多