1

I'm trying to define a class which will have as a field a Set, and would like to be able to manipulate this set directly from the container class:

case class MyClass(prop: String) extends TraversableLike[Int,MyClass] {
   private def mySet: Set[Int]() = Set()

  override def foreach[U](f: Int => U) = data.foreach[U](f)

  override def newBuilder: Builder[Int, MyClass] =
    new ArrayBuffer[Int] mapResult (a => MyClass(prop, a.toSet))

  implicit def canBuildFrom: CanBuildFrom[MyClass, Int, MyClass] =
    new CanBuildFrom[MyClass, Int, MyClass] {
      def apply(): Builder[Int, MyClass] = newBuilder
      def apply(from: MyClass): Builder[Int, MyClass] = newBuilder
    }
}

I'd like to be able to do

var obj = MyClass("hello")
obj += 1
obj = obj map (_+1)

The first instruction (obj+= 1) works, but the second doesn't. The problem is that I can't put my implicit canBuildFrom into an object MyClass because builder needs informations dependant of the instance (in this case, the prop field).

Is there a solution make my implicit accessible and keep its instance dependance ? I'd like to avoid making my class mutable.

1 Answers1

5

There are a couple of problems with your code:

  • Set[Int]() is not a valid type for mySet, you should drop the ()
  • member mySet should be a val, not a def
  • you are calling apply() methods that don't exist
  • if you want to hook in into the collections hierarchy at the Traversable level, you don't get methods like + and the derived +=. If you're representing a set, then it should be a Set.

Here's a revised attempt:

import mutable.Builder
import generic.CanBuildFrom

class MyClass private (val prop: String, private val mySet: Set[Int] = Set())
    extends immutable.Set[Int] with SetLike[Int, MyClass] {

  def -(elem: Int) = MyClass(prop, mySet - elem)
  def +(elem: Int) = MyClass(prop, mySet + elem)
  def contains(elem: Int) = mySet.contains(elem)
  def iterator = mySet.iterator

  override def empty: MyClass = MyClass(prop)

  override def stringPrefix = "MyClass(" + prop + ")"
}

object MyClass {

  def DefaultProp = "DefaultProp"

  def apply(prop: String, mySet: Set[Int] = Set()) = new MyClass(prop, mySet)

  def newBuilder(prop: String = DefaultProp): Builder[Int, MyClass] =
    Set.newBuilder[Int] mapResult (set => MyClass(prop, set))

  implicit def canBuildFrom: CanBuildFrom[MyClass, Int, MyClass] =
    new CanBuildFrom[MyClass, Int, MyClass] {
      def apply(): Builder[Int, MyClass] = newBuilder()
      def apply(from: MyClass): Builder[Int, MyClass] = newBuilder(from.prop)
    }

}

Then you can write:

var obj = MyClass("hello")
obj += 1
println(obj) // prints MyClass(hello)(1)
obj = obj map (_ + 1)
println(obj) // prints MyClass(hello)(2)

Let's dissect that:

MyClass is now explicitly an immutable set with a custom representation declared in the type arguments to SetLike. prop is a public val member; the actual set, mySet, is a private val.

Then we need to implement the four operations on which Set relies, by simply forwarding them the mySet. (This looks a like it could be factored out. For the Seqs, there is a class SeqForwarder that does a similar job; I couldn't find a SetForwarder, though). Finally, we provide an empty method, on which the built-in inherited builder also relies. Finally, overriding stringPrefix enables a nicer string representation with "MyClass" and the value of prop.

Note that The canBuildFrom object MyClass calls newBuilder, passing the prop of the original collection when it can. This means that most of the time, you can keep this value while mapping, etc. over MyClass instances. We need to define a default value for prop, however, since CanBuildFroms must define an apply method that does not tell what the originating collection is. (Question: why would this actually happen?)

Finally, our implementation of newBuilder does not rely on ArrayBuffers any more, but directly builds the Set instance that will be wrapped by your new MyClass instance.

Some more resources:

Community
  • 1
  • 1
Jean-Philippe Pellet
  • 59,296
  • 21
  • 173
  • 234
  • Great answer. The key point of my error was that I was considering that I couldn't put canBuildFrom outside my instance because I needed property, but I didn't saw that the instance was given in the apply() method of canBuildFrom. Thank you ! For the Set vs TraversableLike, I thought it would increase implementation abstraction, but anyway, a Set is already an abstract collection. – Ludovic Patey Apr 30 '11 at 08:45
  • Philippe Your answer perfectly supplements [this question](http://stackoverflow.com/q/5659893/460387) if one wants to have a wrapper of Set with a specific type. With regard to `SetForwarder` I'd love to have more answers on [this question](http://stackoverflow.com/q/5737534/460387)... – Frank S. Thomas Apr 30 '11 at 09:01
  • @Frank For the proxy-vs-forwarder question, I think huynhjl and you have already outlined the differences well, I'd have nothing to add. – Jean-Philippe Pellet Apr 30 '11 at 09:13
  • Philippe By the way, your code changes the equals & hashCode function, because it takes any more only the Set content. I fixed it in my source code, but I just wanted to notice it in case where someone else would read your example. – Ludovic Patey Apr 30 '11 at 13:09