Dmitry Reutov
Dmitry Reutov

Reputation: 3032

Activate lazy variable by name

Let's say I have a case class with a lazy member

case class Person(name: String, surname: String) {
  lazy val initials: String = name(0) + "." + surname(0) + "."
}

And I have a universal function which converts it into Map

def getCCParams(cc: AnyRef) =
  cc.getClass.getDeclaredFields.map { f =>
    f.setAccessible(true)
    f.getName -> f.get(cc)
  }.toMap

now I create a person and get its values

val JohnSmith = Person("John", "Smith")
val res = getCCParams(JohnSmith)
println(res)

thus i get result

HashMap(initials -> null, name -> John, surname -> Smith)

initials equal null because it was not called. Is there any way to activate lazy value inside getCCParams function? The list of lazy members I can pass as a parameter

def getCCParams(cc: AnyRef, lazyMembers: List[String] = List("initials")) = ...

Thank you

Upvotes: 2

Views: 165

Answers (3)

Dmitry Reutov
Dmitry Reutov

Reputation: 3032

Thanx a lot Krzysztof and Mario, finally i did like this

def getCCParams(cc: AnyRef, lazyFields: Set[String] = Set()) =
  {
    val cl = cc.getClass
    val pairs = cl.getDeclaredFields flatMap { f: Field =>
      f.getName match {
        case name: String if name.startsWith("bitmap$") => None
        case name: String =>
          if (lazyFields.contains(name)) {
            cl.getMethod(name).invoke(cc)
          }
          f.setAccessible(true)
          Some(name -> f.get(cc))
      }
    }

    pairs.toMap
  }


val JohnSmith = Person("John", "Smith")
val res = getCCParams(JohnSmith, Set("initials"))

it allows to choose which members i want to activate, which i dont, and allows not include bitmap$ to result

Upvotes: 2

Mario Galic
Mario Galic

Reputation: 48430

Krzysztof's answer works because lazy val initials = "J.S." expands to something like

lazy var initials: String = _;
var bitmap$0: Boolean = _;
private def initials$lzycompute(): String = {
  if (!bitmap$0)
  {
    initials = ("J.S.": String);
    bitmap$0 = true
  };
  initials
};
lazy def initials(): String =
  if (!bitmap$0)
    initials$lzycompute()
  else
    initials;

where we see corresponding public initials as well as private initials$lzycompute methods which actually sets var initials.

Upvotes: 4

Krzysztof Atłasik
Krzysztof Atłasik

Reputation: 22635

Lazy vals are initialized when the accessor method is called the first time. So maybe you could just call these methods instead of trying to get directly to fields?

def getCCParams(cc: AnyRef) = {
  val clazz = cc.getClass
  clazz.getDeclaredFields.flatMap { f =>
    Try(clazz.getMethod(f.getName)) //get the accessor method by name of field
      .toOption
      .map(m => f.getName -> m.invoke(cc))
  }.toMap
}

Result: Map(initials -> J.S., name -> John, surname -> Smith)

Upvotes: 4

Related Questions