Joa Ebert
Joa Ebert

Reputation: 6715

Scala case classes in collections

Scenario: I am parsing an IL and want to convert from a stackbased representation to a CFG for instance.

My IL consists of multiple operations like PushInt(value), Pop etc. The question is now which implementation would be correct in terms of Scala. I would love to use case classes/objects or extractors so that I can write code alà

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

Now the problem exists with a sequence like PushInt(1) :: PushInt(1) :: Pop :: Pop since PushInt(1) is equal to PushInt(1) and I can not add multiple (equal) operations into a collection. However I know I am throwing some information away which is the position in the flow, but this is implicitly stored as te index in the sequence.

So my question is now how to model my structure according to my requirements. Is any of the possible solutions "correct" in terms of Scala?

Upvotes: 6

Views: 2242

Answers (2)

Patrick
Patrick

Reputation: 15717

If Joa don't mind ;) Imagine a code like that:

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

now we construct a list representing a sequence of a program instructions

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

First problem : you want to get the index of an operation

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

Second problem : you want to map each operation from the previous list to a value, this will fail since equals will not distinguish the two Pop, or the two PushInt.

P.S. Of course it is not an answer, i haven`t found how to post this under the others comments feel free to move it at the right place

Upvotes: 2

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297265

First, let's address the problem of finding the exact instance you want:

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

That, of course, mean PushInt(1) != PushInt(1), unless it is the exact same instance of PushInt(1). It doesn't break equals/hashCode contract because a.equals(b) => a.hashCode == b.hashCode, but a.hashCode == b.hashCode doesn't imply anything. But if your only use is finding that instance, try this instead:

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

Either way, if you reinsert that instance in the list you'll have trouble. You have to make sure that each instance you insert is unique. You might even write your own collection, either throwing an exception if a repeated instance is inserted, or make a copy of any instance passed to it (easy enough with case classes and copy method on Scala 2.8).

Upvotes: 5

Related Questions