Alex Neth
Alex Neth

Reputation: 3346

What is a proper way to manage flexible, typed, immutable data structures in Scala?

Right now I have classes like:

abstract class Record {
  // Required fields
  val productCode:Option[String]
  val price:Option[Double]

  // Optional fields
  val notes:Option[String] = None
  val used:Option[Boolean] = Option(false)
}

Then create them:

val r = new Record {
  override val productCode = Option("abc")
  override val price = Option(32.12)
}

A few things to note:

  1. I use Option for the un-optional fields so that a. I don't have to remember which fields are optional b. I can change which fields are optional without changing my interface
  2. The Option stuff adds a lot of noise. I'd love for that not to be there, but I also don't want to use nulls. This is particularly true when taking into account all the calls to getOrElse when I'm using the structure. (I bet there's a clever way for the language to declaratively autobox these.)
  3. This makes mass assignment (which I'm doing because I have an array of fields) difficult if a subclass mixes new fields in, e.g.:

    override val List(productCode, price, discount) = fields // fields is a List

will not compile because discount is not defined in the superclass and therefor not an override. I'm not sure if there is a way to do this.

My main question is:

  1. Is there a better overall way to manage immutable data structures?
  2. Is there a straightforward way to copy a record and change just one value without writing boilerplate code?

e.g. (pseudocode}:

val r2 = r.clone { override val used = true }

I have heard 2.8 introduces something like this for case classes, however in a language that encourages immutable data structures, I'd be surprised to find out this is not easier before 2.8. I'm still in 2.7.

Upvotes: 3

Views: 422

Answers (5)

thSoft
thSoft

Reputation: 22660

Lenses are a great tool to operate on immutable data structures. See this question.

Upvotes: 0

Nikolay Ivanov
Nikolay Ivanov

Reputation: 8935

Well as already said there is no straight forward way in current ( 2.7) Scala to do that, but from my point of view it can be done pretty easily with builder pattern. Code to demonstrate:

abstract class Record {
  // Required fields
  val productCode:Option[String]
  val price:Option[Double]

  // Optional fields
  val notes:Option[String] = None
  val used:Option[Boolean] = Option(false)
}
class RecordBuilder{
  private var _productCode:Option[String] = null
  private var _price:Option[Double] = null

  // Optional fields
  private var _notes:Option[String] = None
  private var _used:Option[Boolean] = Option(false)

  def productCode(in:Option[String]) : RecordBuilder = {
    _productCode = in
    this
  }
  def price(in : Option[Double]) : RecordBuilder = {
    _price = in
    this
  }
  def notes(in:Option[String]) : RecordBuilder = {
    _notes = in
    this
  }
  def used (in : Option[Boolean]) : RecordBuilder = {
    _used = in
    this
  }

  def create() : Record  =  {
   val r =  new Record = {
      override productCode = _productCode
      override price = _price
      override notes = _notes
      override used  = _used

    }
  r
}
}
object Record{
  def from(in:Record) : RecordBuilder = {
    val r = new RecordBuilder
    r.productCode(in.productCode).price(in.price).notes(in.notes)
    .used(in.used)
  }
}
object Test {
  def main(args:Array[String]) = {
    val r = new Record {
    override val productCode = Option("abc")
    override val price = Option(32.12)}
  }
  val r1 = Record.from(r).used(true).create
}

Upvotes: 0

oxbow_lakes
oxbow_lakes

Reputation: 134340

Using Option for a field which is not optional seems insane to me: why?

Upvotes: 0

David Crawshaw
David Crawshaw

Reputation: 10577

This looks to be a problem very much addressed in 2.8:

case class Employee(name: String, age: Int)

val joe = Employee("Joe", 25)
val bob = joe copy (name = "Bob")

Combine this with default values, and the example you give can be easily rewritten as a case class, which I think of as the 'proper' way to implement an immutable data type. (I'm not sure if that's true for scala, but coming from ocaml/haskell, it seems right.)

In 2.7 you're going to have to implement a whole lot of helper functions:

def asUsed(r: Record): Record = {
  Record(r.productCode, r.price, r.nodes, Some(true))
}

Yuck. They should really hurry along 2.8...

Upvotes: 2

Daniel C. Sobral
Daniel C. Sobral

Reputation: 297285

There is no easy way to clone instances. FWIW, immutable data structures are usually deep. For instance, the List class has only two members: hd and tl. A list grows by chaining members.

You clone such structures by creating the minimum amount of new data structures, and refencing as much of the old data structure as possible. Usually, this is done through recursion.

You learn more about this in the book Purely Functional Data Structures. The paper on which the book is based is freely available.

You can look up Scala questions here to see interesting ways to handle Option data. Unfortunately, I don't have any solutions to your other concerns.

Upvotes: 2

Related Questions