1

I have a case class representing my domain:

case class MyModel(rawValue: String, transformedValue: String)

rawValue maps to a value in the database and is properly parsed and bound. What I am trying to do is add transformedValue to my model: this value is just some arbitrary transformation that I perform on the rawValue. It does not map to any data in the database / query.

I have a parser (prior to adding transformedValue) that looks like this:

val parser = {
  get[String]("rawValue") map {
    case rawValue => MyModel(rawValue)
  }
}

Since MyModel is immutible and I can't insert transformedValue into it after its been created, what and where is the best way to do and add this transformation (e.g. adding ad-hoc values to the Model) preferably without using vars?

Coming from Java, I would probably just have added a getTransformedValue getter to the domain class that performs this transformation on the rawValue attribute.

oym
  • 6,983
  • 16
  • 62
  • 88

3 Answers3

2

Since transformedValue seems to be a derived property, and not something that would be supplied in the constructor, you can define it as an attribute in the body of the function, possibly using the lazy qualifier so that it will only be computed on-demand (and just once for instance):

case class MyModel(rawValue: String) {
  lazy val transformedValue: String = {
    // Transformation logic, e.g.
    rawValue.toLowerCase()
  }
}

val is evaluated when defined; def is evaluated when called. A lazy val is evaluated when it is accessed the first time.

It may be appropriate to use lazy in the case of an expensive computation that is rarely needed, or when logic requires it. But for most cases a regular val should be used. Quoting:

lazy val is not free (or even cheap). Use it only if you absolutely need laziness for correctness, not for optimization.

Fernando Correia
  • 21,803
  • 13
  • 83
  • 116
  • Thank you. This looks like the solution I posted before I saw this. I like the idea of using 'lazy'. Could you comment on the differences by using val instead of def (like I did in my solution)? – oym Jul 10 '14 at 18:58
  • `val` is evaluated when defined; `def` is evaluated when called. A `lazy val` is evaluated when it is accessed the first time. See http://stackoverflow.com/a/7484933/376366 – Fernando Correia Jul 10 '14 at 19:54
  • See the updated answer for clarification about when to use lazy val instead of val. – Fernando Correia Jul 10 '14 at 20:05
1

I don't see why you wouldn't just do the transformation in the parser itself:

def transformation(rawValue: String): String = ...

val parser = {
    get[String]("rawValue") map {
        case rawValue => MyModel(rawValue, transformation(rawValue))
    }
}

Or if you don't want to do it there for some reason, you can use copy to create a new copy of MyModel with a new value:

val model = MyModel("value", ..)

val modelWithTransform = model.copy(transformedValue = transformation(model.rawValue))

You could also overload apply to automatically apply the transformation within the companion object:

case class MyModel(rawValue: String, transformedValue: String)

object MyModel {

    def apply(value: String): MyModel = MyModel(rawValue, transformation(rawValue))

    val parser = {
        get[String]("rawValue") map {
            case rawValue => MyModel(rawValue)
        }
    }
}

MyModel may be immutable, but you can always create another copy of it with some values changed.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • Thanks for the input. I added my solution right before I saw this and would value your opinion comparing the two..your solution does appear to be a bit more complex – oym Jul 10 '14 at 18:57
  • @oym Not really, just taking advantage of some syntactic sugar. With this method you'll only ever do the computation once, and it'll be available as soon as it's parsed. – Michael Zajac Jul 10 '14 at 19:01
0

Turns out it was easier than I thought and the solution did look like what I said I would have done in java:

I just add a function to MyModel that does this transformation:

case class MyModel(rawValue: String) {
  def transformedValue = {
    // do the transformation here and return it
  }
}
oym
  • 6,983
  • 16
  • 62
  • 88