Как расширить неизменяемую коллекцию и добавить элемент поля?

Я хотел бы добавить члена someProperty в неизменяемый Set вот так,

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

так что MySet ведет себя как Set. Однако я недостаточно умен, чтобы реализовать Builder/CanBuildFrom (например, здесь), который сохранил бы someProperty после преобразования. Мое единственное решение — вручную связать MySet с картой, foldLeft и т. д., чтобы он вел себя как 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...

}

но это кажется очень утомительным. Есть ли лучший способ сделать это, не попадая на изменяемую территорию? Спасибо.


person Lasf    schedule 03.03.2016    source источник
comment
Если я правильно понимаю, вы находите продление утомительным, но и не растягиваться тоже? :)   -  person slouc    schedule 03.03.2016
comment
@slouc Я нахожу расширение не утомительным, а расширение сложным/невозможным. Хотя желательно продлить.   -  person Lasf    schedule 03.03.2016
comment
Я согласен, что расширение сложно, вы сами предоставили некоторые доказательства этой ссылкой SO. Если вы хотите избежать ручной настройки карт и foldLefts, вы можете выполнить неявное преобразование Set в свой класс, что добавит дополнительный уровень функциональности вашему набору. По сути, вы выбираете композицию вместо наследования. Это также то, что предлагается в ссылке, которую вы предоставили.   -  person slouc    schedule 03.03.2016
comment
Я не хочу добавлять дополнительный уровень функциональности. Я хочу добавить значение члена, для которого неявный класс не поможет.   -  person Lasf    schedule 03.03.2016
comment
@Lasf Почему ваш класс MySet изменяем? Не должен ли это быть только класс case, или Set тоже должен быть val. Вы можете использовать Tuple размера 2 и копировать с помощью операций _._2   -  person mavarazy    schedule 03.03.2016
comment
@Lasf С неявным классом у вас может быть существующий набор, но с дополнительными элементами внутри. Под членами я подразумеваю либо атрибуты, либо методы, говоря на жаргоне объектно-ориентированного программирования. В вашей ссылке есть пример с методом findOdd, но это может быть что угодно. Наверное, я не понимаю, что вы имеете в виду под значением члена   -  person slouc    schedule 03.03.2016
comment
@slouc Атрибут неявного класса будет членом этого неявного класса; это не будет атрибутом Set. Как мне установить значение атрибута при создании экземпляра Set?   -  person Lasf    schedule 03.03.2016
comment
@Lasf Хорошо, но в этом случае я не понимаю, как теоретически возможно сделать что-то еще, кроме как расширить существующий набор или реализовать свой собственный. У вас не может быть Сета, не имея Сета :) Если ваш вопрос о том, как сделать одну из этих двух процедур менее болезненной, то я бы посоветовал вам просто реализовать все с нуля. Когда вам будет трудно реализовать что-то, вы, по крайней мере, будете знать, что это потому, что вам это нужно, а не потому, что этого требует библиотека Scala.   -  person slouc    schedule 03.03.2016
comment
@slouc Верно. Так что да, это мой вопрос. Если есть способ реализовать Builder/CanBuildFrom, я хотел бы знать. Сложный вопрос... посмотрим, будет ли на него ответ.   -  person Lasf    schedule 03.03.2016
comment
Комментарий к реализации HashSet: детали реализации неизменяемых наборов хэшей делают наследование от них неразумным. ржунимагу   -  person vitalii    schedule 03.03.2016
comment
@vitalii да, я видел это, когда пытался решить эту проблему. Вздох... лол   -  person Lasf    schedule 03.03.2016
comment
Ваши дополнительные данные имеют хорошее значение по умолчанию? Должен ли этот T быть каким-то фиксированным типом или параметром типа также и для MySet (т.е. MySet[A, T])?   -  person Kolmar    schedule 03.03.2016
comment
@Kolmar Фиксированный тип. На самом деле это может быть один из трех case-объектов: Must, MustNot или Should, представляющий логический префикс для набора... но я чувствую, что это запутает вопрос, поэтому я просто оставил его как некоторый произвольный тип T.   -  person Lasf    schedule 03.03.2016


Ответы (1)


Прежде всего, наличие значения по умолчанию (нуля) для someProperty немного упрощает задачу. В вашем случае, я думаю, вы могли бы выбрать Must или MustNot, в зависимости от специфики вашей проблемы.

Я предполагаю следующее определение T и его значение по умолчанию:

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

Вы можете иметь следующую реализацию для MySet, перенося большинство операций на его атрибут set, а некоторые — на сопутствующий ему объект. Кроме того, обратите внимание, что некоторые методы, такие как filter, не используют CanBuildFrom, поэтому для них необходимо переопределить метод newBuilder.

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)
}

Что касается компаньона object MySet, можно расширить SetFactory[MySet] или какой-либо другой базовый класс объектов-компаньонов коллекции. Это дает реализации MySet.empty[A] и MySet.apply[A](as: A*), которые создают MySet, используя значение по умолчанию 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]
    }
}
person Kolmar    schedule 03.03.2016
comment
Я не спец в этом, поэтому любые предложения и исправления приветствуются. Во всяком случае, это, кажется, работает. - person Kolmar; 03.03.2016