【问题标题】:How to extend an immutable collection and add a field member?如何扩展不可变集合并添加字段成员?
【发布时间】:2016-03-03 16:13:59
【问题描述】:

我想像这样将成员 someProperty 添加到不可变的 Set

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

这样MySet 的行为就像Set。但是,我还不够聪明,无法实现Builder/CanBuildFrom(例如here),它会在转换后保留someProperty。我唯一的解决方案是手动将 MySet 与 map、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...

}

但这似乎很乏味。有没有更好的方法可以在不进入可变领域的情况下做到这一点?谢谢。

【问题讨论】:

  • 如果我理解正确,你觉得扩展乏味,但你发现扩展不乏味? :)
  • @slouc 我发现不扩展乏味,扩展困难/不可能。不过最好延长。
  • 我同意扩展很困难,您通过该 SO 链接自己提供了一些证据。如果您想避免手动设置地图和 foldLefts,您可以进行从 Set 到您的类的隐式转换,从而为您的集合添加额外的功能层。基本上你正在接受composition over inheritance。这也是您提供的链接中建议的内容。
  • 我不想添加额外的功能层。我想添加一个成员值,隐式类对此无济于事。
  • @Lasf 为什么你的 MySet 类是可变的?它不应该是唯一的案例类,或者 Set 也应该是一个 val。您可以使用大小为 2 的元组,并使用 _._2 操作进行复制

标签: scala scala-2.11


【解决方案1】:

首先,为someProperty 设置一个默认(零)值会使事情变得更容易一些。在你的情况下,我想你可以选择MustMustNot,这取决于你的问题的具体情况。

我将假设 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*) 的实现,它们使用someProperty 的默认值创建MySet

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

【讨论】:

  • 我不是这方面的专家,因此欢迎任何建议和更正。无论如何,这似乎有效。
最近更新 更多