How to implement instance sharing for case classes


Assuming defintion:

case class IntegerWrapper(i : Int)

and being in a situation that potentially large amounts of IntegerWrapper instances with i=[0..N> may be created what does one have to do to:

  1. Map this range to a fixed set of singletons [IntegerWrapper(0) .. IntegerWrapper(N)>

  2. Preserve existing value semantics for class IntegerWrapper (matching, equals, hashcode, serialization)

I am looking to do instance sharing akin to what java.lang.Integer does. I guess my question is if this can be done without having to do everything myself. Simply defining a companion object with an apply(i : Int) does not compile. Any suggestions?

Something like this?

sealed trait IntegerWrapper {
  def i: Int

object IntegerWrapper extends (Int => IntegerWrapper) {
  private case class IntegerWrapperImpl(i: Int) extends IntegerWrapper

  private val instances: List[IntegerWrapperImpl] = ...
    /* Wrapper instances for i in [0..N) */

  def apply(i: Int): IntegerWrapper = instances(i)

  def unapply(iw: IntegerWrapper): Option[Int] = Some(iw.i)

The advantage is that equals and hashCode are still generated by the compiler, since IntegerWrapperImpl is a case class. The disadvantage is that you cannot directly use other compiler-added case class goodies, e.g., copy. If you want to use that, either expose IntegerWrapperImpl to the client, or, IMHO better, add copy to the IntegerWrapper interface.

Pattern matching works as usual:

val iw0 = IntegerWrapper(0)
val iw1: IntegerWrapper = IntegerWrapper(1)

iw0 match {
  case IntegerWrapper(0) => println("IW(0)")
  case _ => println("something else")
} // IW(0)

iw1 match {
  case IntegerWrapper(1) => println("IW(1)")
  case _ => println("something else")
} // IW(1)

iw1 match {
  case IntegerWrapper(2) => println("IW(2)")
  case _ => println("something else")
} // something else