2

I would like to add a member someProperty to an immutable Set like this,

class MySet[A](val someProperty: T, set: Set[A]) 
  extends Set[A] with SetLike[A, MySet[A]] { 
  //... 
}

such that MySet behaves like a Set. However, I am not clever enough to implement a Builder/CanBuildFrom (eg. here) that would retain someProperty following a transformation. My only solution is to manually wire up MySet with map, foldLeft, etc., such that it behaves like a Set

class MySet[A](val someProperty: T, set: Set[A]) {

  def map[B](f: (A) => B)(implicit bf: CanBuildFrom[Set[A], B, Set[B]]): MySet[B] =
    new MySet[B](someProperty, set.map[B, Set[B]](f)(bf))

  //more methods here...

}

but this seems very tedious. Is there a better way to do this without getting into mutable territory? Thanks.

Community
  • 1
  • 1
Lasf
  • 2,536
  • 1
  • 16
  • 35
  • If I understand correctly, you find extending tedious, but you find not extending tedious as well? :) – slouc Mar 03 '16 at 16:37
  • @slouc I find not extending tedious, and extending difficult/impossible. Would be preferable to extend though. – Lasf Mar 03 '16 at 16:41
  • I agree that extending is difficult, you provided some proof yourself with that SO link. If you want to avoid manually setting up maps and foldLefts, you can have an implicit conversion from Set to your class which adds an extra layer of functionality to your set. Basically you're taking [composition over inheritance](https://en.wikipedia.org/wiki/Composition_over_inheritance). This is also what is suggested in the link you provided. – slouc Mar 03 '16 at 16:46
  • I don't want to add an extra layer of functionality. I want to add a member value, for which implicit class would not help. – Lasf Mar 03 '16 at 16:52
  • @Lasf Why is your MySet class mutable? Should not it be only case class, or Set should also be a val. You can use Tuple of size 2, and copy with _._2 operations – mavarazy Mar 03 '16 at 16:54
  • @Lasf With an implicit class you can have an existing set, but with extra members inside. By members I mean either attributes or methods, speaking in OO jargon. Your link has an example with method findOdd but it can be anything. I guess I don't understand what you mean by "member value" – slouc Mar 03 '16 at 17:02
  • @slouc An implicit class's attribute would be a member of that implicit class; it would not be an attribute of the `Set`. How would I set the the value of the attribute when I instantiate the `Set`? – Lasf Mar 03 '16 at 17:30
  • @Lasf OK, but in that case I don't see how is it theoretically possible to do anything else other than extend an existing Set or implement your own. You cannot have a Set without having a Set :) If your question is about guidance as to how to make one of those two procedures less painful, then I would advise you to simply implement everything from scratch. When you will have a rough time implementing something, at least you'll know that it's because you need it rather than because Scala library demands it. – slouc Mar 03 '16 at 17:37
  • @slouc Right. So yeah that's my question. If there is a way to implement the `Builder`/`CanBuildFrom` I'd love to know. It's a tough question... let's see if it gets an answer. – Lasf Mar 03 '16 at 17:39
  • Comment on HashSet implementation: "The implementation details of immutable hash sets make inheriting from them unwise." lol – vitalii Mar 03 '16 at 18:18
  • @vitalii yeah I saw that while I was trying to tackle this. Sigh... lol – Lasf Mar 03 '16 at 18:25
  • Does your additional data have a good default value? Should this `T` be some fixed type or a type parameter to `MySet` as well (i.e. `MySet[A, T]`)? – Kolmar Mar 03 '16 at 19:18
  • @Kolmar Fixed type. In reality, it can be one of three case objects: `Must`, `MustNot` or `Should`, representing a logical prefix for the set... but I feel like that'd muddy the question so I just left it as some arbitrary type `T`. – Lasf Mar 03 '16 at 19:22

1 Answers1

1

First of all, having a default (zero) value for someProperty makes things a bit easier. In your case, I guess you could choose Must or MustNot, depending on the specifics of your problem.

I'll assume the following definition of T and its default:

sealed trait T
object T {
  final val Default: T = Must
}
case object Must extends T
case object MustNot extends T
case object Should extends T

You can have the following implementation for MySet, deferring most operations to its set attribute, and some to its companion object. Also, note that some methods like filter don't use CanBuildFrom, so you have to override newBuilder method for them.

class MySet[A](val someProperty: T, set: Set[A])
  extends Set[A] with SetLike[A, MySet[A]] {

  def +(elem: A): MySet[A] = new MySet[A](someProperty, set + elem)
  def -(elem: A): MySet[A] = new MySet[A](someProperty, set - elem)
  def contains(elem: A): Boolean = set contains elem
  def iterator: Iterator[A] = set.iterator

  override def companion = MySet
  override def empty: MySet[A] = MySet.empty[A]
  // Required for `filter`, `take`, `drop`, etc. to preserve `someProperty`.
  override def newBuilder: mutable.Builder[A, MySet[A]] = 
    MySet.newBuilder[A](someProperty)
}

As for the companion object MySet, it's possible to extend SetFactory[MySet] or some other base class of collection companion objects. This gives implementations of MySet.empty[A] and MySet.apply[A](as: A*), which create MySet using the default value of someProperty.

object MySet extends SetFactory[MySet] {
  // For the builder you can defer to the standard `mutable.SetBuilder`
  class MySetBuilder[A](someProperty: T) extends 
    mutable.SetBuilder[A, MySet[A]](new MySet(someProperty, Set.empty))

  def newBuilder[A] = newBuilder[A](T.Default)
  // Additional method for creating a builder with a known value of `someProperty`
  def newBuilder[A](someProperty: T) = new MySetBuilder[A](someProperty)

  // You may also want to define `apply` and `empty` methods
  //   that take a known `someProperty`.

  // `CanBuildFrom` from `MySet[_]` to `MySet[A]`.
  implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, MySet[A]] =
    new CanBuildFrom[Coll, A, MySet[A]] {
      // This is the method that makes
      //   e.g. `map`, `flatMap`, `collect` preserve `someProperty`
      def apply(from: Coll): mutable.Builder[A, MySet[A]] = 
        newBuilder[A](from.someProperty)
      def apply(): mutable.Builder[A, MySet[A]] = newBuilder[A]
    }
}
Kolmar
  • 14,086
  • 1
  • 22
  • 25
  • I'm not really an expert on this, so any suggestions and corrections are welcome. Anyway, this seems to work. – Kolmar Mar 03 '16 at 20:46