views:

154

answers:

4

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.

+2  A: 

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.

Daniel
This makes sense for Collection objects, but not for structs.
Alex Neth
Well, actually, structs as well. Classes with a large amount of fields are not recommended. Rather, the recommended approach is to break it in aggregates of _Value Objects_. In Scala, I suppose you could do so with Trais, which would make them part of the class instead of aggregates. The advantage would be that each trait could have its own clone method, making the boilerplate more reusable.
Daniel
+2  A: 

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...

David Crawshaw
Selfishly, I hope you are not correct ;)
Alex Neth
A: 

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

oxbow_lakes
I provide the reason above.Otherwise, if I change a field from optional to required. I suppose I could use Some in this case, but it doesn't really matter, as my meaning is to require the subclass to declare the value which an abstract val effectively does.
Alex Neth
Yes, I agree, I should be using Some here so that None is not a valid value. However Some(null) would still unfortunately be valid.
Alex Neth
Even just `null` would be a valid assignment to a variable of type `Some`. There is a `NotNull` trait you can use to some extent to avoid undesirable nullness.
Daniel
A: 

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
}
Nikolay Ivanov
This is a way of building the immutable structure as an alternative to overriding the vals on construction, but does not copy an arbitrary list. Imagine this with 20 fields. It's a mess of the kind of boiler-plate code that scared me into trying out Scala.
Alex Neth
Well i mentioned this way more for copying than for construction, and unfortunately if you'll create a subclass of Record you are going to need another builder class, it's actually same as implementing clone()you subclass something clone able you usually have to override clone(). By the way , why don't you passing mandatory parameters into the constructor? Like class Foo(val a:String,val b:String) {}
Nikolay Ivanov