【问题标题】:How can I extend Scala collections with member values?如何使用成员值扩展 Scala 集合?
【发布时间】:2015-04-10 19:23:37
【问题描述】:

假设我有以下数据结构:

case class Timestamped[CC[M] < Seq[M]](elems : CC, timestamp : String)

所以它本质上是一个带有属性的序列——一个时间戳——附加到它上面。这很好用,我可以使用语法创建新实例

val t = Timestamped(Seq(1,2,3,4),"2014-02-25")
t.elems.head // 1
t.timestamp  // "2014-05-25"

语法笨拙,相反,我希望能够执行以下操作:

Timestamped(1,2,3,4)("2014-02-25")
t.head      // 1
t.timestamp // "2014-05-25"

timestamped 只是 Seq 的扩展,它是 SeqLike 的实现,具有单个属性 val timestamp : String

这似乎很容易做到;只需使用 Seq 和 mixin TimestampMixin { val timestamp : String }。但我不知道如何创建构造函数。 我的问题是:如何在伴生对象中创建一个构造函数,创建一个带有额外成员值的序列?签名如下:

object Timestamped {
  def apply(elems: M*)(timestamp : String) : Seq[M] with TimestampMixin = ???
}

您会发现这并不简单;集合使用Builders 来实例化自己,所以我不能简单地调用构造函数来覆盖一些vals。

【问题讨论】:

    标签: scala


    【解决方案1】:

    归根结底,Scala 集合是非常复杂的结构。扩展Seq 需要实现applylengthiterator 方法。最后,您可能最终会为ListSet 或其他东西复制现有代码。您可能还需要为您的收藏担心CanBuildFroms,如果您只想添加一个字段,我认为这最终是不值得的。

    请考虑从您的 Timestamped 类型到 Seq 的隐式转换。

    case class Timestamped[A](elems: Seq[A])(timestamp: String)
    
    object Timestamped {
        implicit def toSeq[A](ts: Timestamped[A]): Seq[A] = ts.elems
    }
    

    现在,每当我尝试从Seq 调用方法时,编译器都会将Timestamped 隐式转换为Seq,我们可以正常进行。

    scala> val ts = Timestamped(List(1,2,3,4))("1/2/34")
    ts: Timestamped[Int] = Timestamped(List(1, 2, 3, 4))
    
    scala> ts.filter(_ > 2)
    res18: Seq[Int] = List(3, 4)
    

    这里有一个主要缺点,就是在对原始的Timestamped 执行操作之后,我们现在卡在Seq 上。

    【讨论】:

    • 考虑这个 B 计划。
    • 我认为如果您需要从头开始构建 Seq 扩展,它们可能会很复杂,但是扩展 Seq 并将 3 个抽象成员传递给现有实现可以正常工作。非抽象成员只是在我期望的默认实现中使用这 3 种方法。
    • 是的,但是返回集合的方法仍然会返回Seq,就像这里的情况一样。我的观点是,如果他想要一个返回Timestamped[A]map,那么这将非常困难。
    • 是的,正如@m-z 所说,我希望它看起来像一个新的集合类型。我尝试的第一件事是实现BuilderCanBuildFrom;但是,尝试扩展Builder 的现有实现似乎不起作用,因为它们是合格的final,所以我不能重写构造函数来接受我的新timestamp 成员。
    【解决方案2】:

    反过来……扩展 Seq,它只有 3 个抽象成员:

      case class Stamped[T](elems: Seq[T], stamp: Long) extends Seq[T] {
        override def apply(i: Int) = elems.apply(i)
        override def iterator = elems.iterator
        override def length = elems.length
      }
    
      val x = Stamped(List(10,20,30), 15L)
    
      println(x.head)              // 10
      println(x.timeStamp)         // 15
      println(x.map { _ * 10})     // List(100, 200, 300)
      println(x.filter { _ > 20})  // List(30)
    

    请记住,这仅在 Seq 对您的用例足够具体的情况下才有效,如果您以后发现需要更复杂的收集行为,这可能会变得站不住脚。

    编辑:添加了更接近您尝试创建的签名的版本。不确定这是否对您有帮助:

      case class Stamped[T](elems: T*)(stamp: Long) extends Seq[T] {
        def timeStamp = stamp
        override def apply(i: Int) = elems.apply(i)
        override def iterator = elems.iterator
        override def length = elems.length
      }  
    
      val x = Stamped(10,20,30)(15L)
    
      println(x.head)              // 10
      println(x.timeStamp)         // 15
      println(x.map { _ * 10})     // List(100, 200, 300)
      println(x.filter { _ > 20})  // List(30)
    

    elems 最终会成为一般创建的 WrappedArray

    【讨论】:

    • 感谢@Rich Henry。实现看起来确实更简单。但就像你说的,这将强制实现使用Seq。我真正想要的是GenTraversableFactory trait - 请参阅 scaladoc。所有具体的集合伴随对象都扩展了它以获得几乎免费的工厂实现。
    • 跟进上述内容,我很感激我没有在问题中明确说明(这就是为什么第一个代码 sn-p 有CC &lt;: Seq,就像在 GenTraversableFactory 的实现中一样)。也许我需要澄清这一点。不过感谢您的更新。
    • 在您的代码中,CC 必须是 Seq,就像我的一样,假设 &lt; 应该是 &lt;:&lt;%。如果不扩展集合特征/类,使用上面显示的 @m-z 之类的隐式函数,或手动实现方法——您的类将永远不会像问题中所示的集合。
    猜你喜欢
    • 2013-04-23
    • 1970-01-01
    • 1970-01-01
    • 2011-03-04
    • 2016-07-08
    • 1970-01-01
    • 2018-05-23
    • 2017-02-26
    相关资源
    最近更新 更多