Eduardo
Eduardo

Reputation: 8392

Allocation of Function Literals in Scala

I have a class that represents sales orders:

class SalesOrder(val f01:String, val f02:Int, ..., f50:Date)

The fXX fields are of various types. I am faced with the problem of creating an audit trail of my orders. Given two instances of the class, I have to determine which fields have changed. I have come up with the following:

class SalesOrder(val f01:String, val f02:Int, ..., val f50:Date){

  def auditDifferences(that:SalesOrder): List[String] = {

    def diff[A](fieldName:String, getField: SalesOrder => A) =
      if(getField(this) != getField(that)) Some(fieldName) else None

    val diffList = diff("f01", _.f01) :: diff("f02", _.f02) :: ...
                :: diff("f50", _.f50) :: Nil

    diffList.flatten
  }    
}

I was wondering what the compiler does with all the _.fXX functions: are they instanced just once (statically), and can be shared by all instances of my class, or will they be instanced every time I create an instance of my class?

My worry is that, since I will use a lot of SalesOrder instances, it may create a lot of garbage. Should I use a different approach?

Upvotes: 4

Views: 150

Answers (2)

Travis Brown
Travis Brown

Reputation: 139028

One clean way of solving this problem would be to use the standard library's Ordering type class. For example:

class SalesOrder(val f01: String, val f02: Int, val f03: Char) {
  def diff(that: SalesOrder) = SalesOrder.fieldOrderings.collect {
    case (name, ord) if !ord.equiv(this, that) => name
  }
}

object SalesOrder {
  val fieldOrderings: List[(String, Ordering[SalesOrder])] = List(
    "f01" -> Ordering.by(_.f01),
    "f02" -> Ordering.by(_.f02),
    "f03" -> Ordering.by(_.f03)
  )
}

And then:

scala> val orderA = new SalesOrder("a", 1, 'a')
orderA: SalesOrder = SalesOrder@5827384f

scala> val orderB = new SalesOrder("b", 1, 'b')
orderB: SalesOrder = SalesOrder@3bf2e1c7

scala> orderA diff orderB
res0: List[String] = List(f01, f03)

You almost certainly don't need to worry about the perfomance of your original formulation, but this version is (arguably) nicer for unrelated reasons.

Upvotes: 2

0__
0__

Reputation: 67280

Yes, that creates 50 short lived functions. I don't think you should be worried unless you have manifest evidence that that causes a performance problem in your case.

But I would define a method that transforms SalesOrder into a Map[String, Any], then you would just have

trait SalesOrder {
  def fields: Map[String, Any]
}
def diff(a: SalesOrder, b: SalesOrder): Iterable[String] = {
  val af = a.fields
  val bf = b.fields 
  af.collect { case (key, value) if bf(key) != value => key }
}

If the field names are indeed just incremental numbers, you could simplify

trait SalesOrder {
  def fields: Iterable[Any]
}
def diff(a: SalesOrder, b: SalesOrder): Iterable[String] =
  (a.fields zip b.fields).zipWithIndex.collect { 
    case ((av, bv), idx) if av != bv => f"f${idx + 1}%02d"
  }

Upvotes: 1

Related Questions