简体   繁体   中英

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, eg:

    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?

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

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.

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

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

对于不是可选的字段使用Option对我来说似乎很疯狂:为什么?

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM