9

I'm trying to write a generic method to iterate over a case class's fields :

case class PriceMove(price: Double, delta: Double)

def log(pm : PriceMove) { info("price -> " + price + " delta -> " + delta)}

I need to make log able to handle any case class. What needs to be the argument type for log to handle case classes only and the actual generic field iteration code?

212
  • 915
  • 3
  • 9
  • 19
  • 1
    Why not just use case classes' `.toString`? It produces very nice output. Your PriceMove could be logged as `PriceMove(0.00,0.00)` – serejja Apr 17 '14 at 08:43
  • I'm already using that but it's not readable for classes with a large number of fields. I need a name value pair for it to be of any real use in logging. – 212 Apr 17 '14 at 08:47
  • 2
    Please consider this question: http://stackoverflow.com/questions/17223213/scala-macros-making-a-map-out-of-fields-of-a-class-in-scala – serejja Apr 17 '14 at 08:51
  • 2
    And this: http://stackoverflow.com/questions/1226555/case-class-to-map-in-scala – serejja Apr 17 '14 at 08:52
  • 1
    Use a macro http://stackoverflow.com/questions/17223213/scala-macros-making-a-map-out-of-fields-of-a-class-in-scala – jilen Apr 17 '14 at 14:03

2 Answers2

16

Okay, considering the two questions I attached to the question, here is what I'd use:

object Implicits {
  implicit class CaseClassToString(c: AnyRef) {
    def toStringWithFields: String = {
      val fields = (Map[String, Any]() /: c.getClass.getDeclaredFields) { (a, f) =>
        f.setAccessible(true)
        a + (f.getName -> f.get(c))
      }

      s"${c.getClass.getName}(${fields.mkString(", ")})"
    }
  }
}

case class PriceMove(price: Double, delta: Double)

object Test extends App {
  import Implicits._
  println(PriceMove(1.23, 2.56).toStringWithFields)
}

This produces:

PriceMove(price -> 1.23, delta -> 2.56)
serejja
  • 22,901
  • 6
  • 64
  • 72
1

I'm afraid there is no easy way to achieve what you're after, as you can't easily get the field names from the case class as discussed here: Reflection on a Scala case class and Generic customisation of case class ToString.

You can try using reflection (though you can guarantee the order of the fields) or tools.nsc.interpreter.ProductCompletion, but both solutions are significantly more complex then you'd really expect.

Community
  • 1
  • 1
Norbert Radyk
  • 2,608
  • 20
  • 24
  • I guess it depends on what you mean by "easy," but it's definitely feasible. I wrote an extensible, customizable rendering library for `Product` based on the reflection information that I obtain using the technique illustrated in "http://stackoverflow.com/a/16097409/20016". The only lesson I had to learn on top of that is that the entirety of the reflection library in 2.10 is **not thread safe!** – Randall Schulz Apr 17 '14 at 15:29
  • @Randall, I agree it's possible, although especially in this particular case (enhancing logging) it does sound like an overkill instead of just overriding the `toString` method for a couple of case classes (in the end they can only have up to 22 fields, so we're not talking some unmanageable numbers here :). Just out of interest, have you done any performance stats for your library (as given the 'PriceMove' class name and having spent last couple of years in finance reflection sounds to me as a potential bottleneck for logging of frequent price ticks). – Norbert Radyk Apr 17 '14 at 16:52
  • I have not benchmarked it. I cache all the reflection information and all the parameterization comes from writing or extending and overriding some built-in formatting parameter types, so there's no reason to expect performance to be poor. I also have complementary rendering for nested combinations of `Map`, `Seq` and `Set` that works in a similar fashion. Lastly I'll add that while this was done for my employer and I cannot unilaterally open it, we are working towards open-sourcing our generic Scala "utility" library where this and other goodies reside. – Randall Schulz Apr 17 '14 at 17:19