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?
Scala case classes are just regular classes which are immutable by default and decomposable through pattern matching. It uses equal method to compare instance structurally. It does not use new keyword to instantiate object. All the parameters listed in the case class are public and immutable by default. Syntax.
A case class has all of the functionality of a regular class, and more. When the compiler sees the case keyword in front of a class , it generates code for you, with the following benefits: Case class constructor parameters are public val fields by default, so accessor methods are generated for each parameter.
The Scala interface for Spark SQL supports automatically converting an RDD containing case classes to a DataFrame. The case class defines the schema of the table. The names of the arguments to the case class are read using reflection and they become the names of the columns.
What is Scala Case Class? A Scala Case Class is like a regular class, except it is good for modeling immutable data. It also serves useful in pattern matching, such a class has a default apply() method which handles object construction. A scala case class also has all vals, which means they are immutable.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With