Reputation: 713
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:
Map this range to a fixed set of singletons [IntegerWrapper(0) .. IntegerWrapper(N)>
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?
Upvotes: 5
Views: 478
Reputation: 12852
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
Upvotes: 2
Reputation: 92056
Memoize the results! With Scala, you can even abstract away all the required logic.
The infrastructure you need:
scala> :paste
// Entering paste mode (ctrl-D to finish)
// A little memoization utility
object Memoization extends Memoization
trait Memoization {
trait Memoizable[A] {
def memoize(fun: A): A
}
implicit def fun1memoizable[A, B] = new Memoizable[A => B] {
def memoize(f: A => B): (A => B) = new MemoizedFunction(f)
}
implicit def fun2memoizable[A, B, C] = new Memoizable[(A, B) => C] {
def memoize(f: (A, B) => C): (A, B) => C = {
val memo = new MemoizedFunction(f.tupled)
(a, b) => memo((a, b))
}
}
implicit def fun3memoizable[A, B, C, D] = new Memoizable[(A, B, C) => D] {
def memoize(f: (A, B, C) => D): (A, B, C) => D = {
val memo = new MemoizedFunction(f.tupled)
(a, b, c) => memo((a, b, c))
}
}
def memoize[F](f: F)(implicit m: Memoizable[F]): F = m memoize f
class MemoizedFunction[-A, +B](f: A => B) extends (A => B) {
private[this] val cache = collection.mutable.Map.empty[A, B]
def apply(a: A): B = cache.getOrElseUpdate(a, f(a))
}
}
import Memoization._
// Abstracting flyweight pattern
// (http://en.wikipedia.org/wiki/Flyweight_pattern)
trait Flyweight[Args, Obj] { self: (Args => Obj) =>
val on: Args => Obj = memoize(this : (Args => Obj))
}
// Ctrl+D
Usage:
scala> :paste
// Entering paste mode (ctrl-D to finish)
case class IntegerWrapper private(i: Int) {
println(this.toString + " created.")
}
object IntegerWrapper extends (Int => IntegerWrapper)
with Flyweight[Int, IntegerWrapper]
// Ctrl+D
scala> IntegerWrapper.on(11)
IntegerWrapper(11) created.
res0: IntegerWrapper = IntegerWrapper(11)
scala> IntegerWrapper.on(11)
res1: IntegerWrapper = IntegerWrapper(11)
This is a general solution and uses a Map
. You might be better off using a Vector
for your particular case.
Upvotes: 4
Reputation: 3068
If you're just trying to avoid allocations, perhaps what you want is a value class? In Scala 2.10, if your IntegerWrapper
class extends AnyVal
, instances usually won't get allocated, and instead static methods will be called on the value itself. For example:
scala> case class IntegerWrapper(val i: Int) extends AnyVal { def increment = i + 1 }
defined class IntegerWrapper
scala> object Test { IntegerWrapper(2).increment }
defined module Test
scala> :javap -verbose Test
...
public Test$();
Code:
Stack=2, Locals=1, Args_size=1
0: aload_0
1: invokespecial #13; //Method java/lang/Object."<init>":()V
4: aload_0
5: putstatic #15; //Field MODULE$:LTest$;
8: getstatic #20; //Field IntegerWrapper$.MODULE$:LIntegerWrapper$;
11: iconst_2
12: invokevirtual #24; //Method IntegerWrapper$.extension$increment:(I)I
15: pop
16: return
Note that the extension method being called there is Int => Int
.
For comparison, here's what you get if you don't extend AnyVal
:
scala> :javap -verbose Test
...
public Test$();
Code:
Stack=3, Locals=1, Args_size=1
0: aload_0
1: invokespecial #13; //Method java/lang/Object."<init>":()V
4: aload_0
5: putstatic #15; //Field MODULE$:LTest$;
8: new #17; //class IntegerWrapper
11: dup
12: iconst_2
13: invokespecial #20; //Method IntegerWrapper."<init>":(I)V
16: invokevirtual #24; //Method IntegerWrapper.increment:()I
19: pop
20: return
With this version, you can see both the object allocation, and the call to a method of the new IntegerWrapper
instance.
Upvotes: 6
Reputation: 32719
This is basically the same use case as for scala's Symbol
class. So you could have a look at Symbol.scala as an inspiration for a reasonable implementation (in particular, an implementation that won't keep the IntegerWrapper instances in memory forever, even when not needed).
Upvotes: 3