Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 736
Silvio Bierman Avatar asked Nov 05 '12 12:11

Silvio Bierman


People also ask

What is case class in Scala syntax of case class?

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.

What are case classes?

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.

What is a case class in spark?

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 does case class mean in Scala?

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.


2 Answers

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.

like image 74
Steve Avatar answered Nov 02 '22 23:11

Steve


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.

like image 21
missingfaktor Avatar answered Nov 03 '22 01:11

missingfaktor