0

In a previous SO post I asked about an idiomatic way to make a container class wrapping an immutable collection thread-safe. Answers that I received all involved using various flavors of read/write locks or synchronization which is not what I wanted.

Let me ask a different question. How do I make the following class that wraps an immutable container immutable? The methods add/remove need to return a new MyContainer class instance suitably altered, but I can't quite see how to do it...

class MyContainer[A] {

  // method that returns a new MyContainer that includes the additional thing...
  def add(thing: A): MyContainer[A] = {
    ???
  }

  def filter(p: A => Boolean): Option[Iterable[A]] = {
    val filteredThings = backingStore.values.filter(p)
    if (filteredThings.isEmpty) None else Some(filteredThings)
  }

  // method that returns a new MyContainer that does not include the thing with given uuid
  def remove(uuid: UUID): MyContainer[A] = {
    ???
  }

  @ volatile private[this] var backingStore = immutable.HashMap.empty[UUID, A]

}

Thoughts?

EDIT: In response to comment, one possible solution would be something similar to the following...

class MyContainer[A](val backingStore: immutable.HashMap[UUID, A]) {

  def add(thing: A): MyContainer[A] = {
    new MyContainer(backingStore + (thing.uuid -> thing))
  }

  def filter(p: A => Boolean): Option[Iterable[A]] = {
    val filteredThings = backingStore.values.filter(p)
    if (filteredThings.isEmpty) None else Some(filteredThings)
  }

  def remove(uuid: UUID): MyContainer[A] = {
    new MyContainer(backingStore - uuid)
  }

}

...backingStore is no longer private (but could put private in constructor). More thoughts?

Community
  • 1
  • 1
davidrpugh
  • 4,363
  • 5
  • 32
  • 46
  • start by making `backingStore` a `val` instead of a `@volatile var` and see where that leads... (edit: typo) – Brian Kent Sep 19 '16 at 01:52
  • To make `backingStore` itself private (but not the constructor), you can just remove `val` (or be explicit and write `private val`). – Alexey Romanov Sep 19 '16 at 06:43

1 Answers1

1

You need a way to construct a new MyContainer that already contains some elements and preferably maintain the same UUIDs. That means you will essentially need a constructor that initalizes backingStore. However, if you don't want to expose it in any way, you can make the constructor private, and provide an overloaded constructor that only allows external code to create an empty collection to begin with (let's say). backingStore can simply be moved into a private constructor for this.

class MyContainer[A] private (backingStore: HashMap[UUID, A]) {

  def this() = this(HashMap.empty[UUID, A])

  def add(thing: A): MyContainer[A] = {
    val uuid: UUID = UUID.randomUUID() // or however the UUID is generated
    new MyContainer(backingStore + ((uuid, thing)))
  }

  def remove(uuid: UUID): MyContainer[A] =
    new MyContainer(backingStore - uuid)

}

scala> val container = new MyContainer[String]()

scala> container.add("a").add("b").add("c")
res2: MyContainer[String] = MyContainer@4a183d02

It's really up to you want you want to expose in the API, though. I wasn't sure what you were going for with filter so I removed it from my example.

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
  • Interesting! Didn't know about private constructors. I included `filter` in order to demonstrate that I am wrapping the underlying immutable collection in order to only expose a subset of its functionality to users. I am interested in maintaining immutability in order to guarantee thread-safety. – davidrpugh Sep 19 '16 at 02:52