【问题标题】:Extending collection classes with extra fields in Scala在 Scala 中使用额外字段扩展集合类
【发布时间】:2011-09-20 08:32:00
【问题描述】:

我正在寻找一个基本上是一个带有额外字段的集合的类。但是,我一直遇到问题,想知道实现它的最佳方法是什么。我试图遵循 Scala 书中给出的模式。例如

import scala.collection.IndexedSeqLike
import scala.collection.mutable.Builder
import scala.collection.generic.CanBuildFrom
import scala.collection.mutable.ArrayBuffer

class FieldSequence[FT,ST](val field: FT, seq: IndexedSeq[ST] = Vector())
        extends IndexedSeq[ST] with IndexedSeqLike[ST,FieldSequence[FT,ST]] {

    def apply(index: Int): ST = return seq(index)
    def length = seq.length

    override def newBuilder: Builder[ST,FieldSequence[FT,ST]]
        = FieldSequence.newBuilder[FT,ST](field)
}

object FieldSequence {

    def fromSeq[FT,ST](field: FT)(buf: IndexedSeq[ST])
        = new FieldSequence(field, buf)

    def newBuilder[FT,ST](field: FT): Builder[ST,FieldSequence[FT,ST]]
        = new ArrayBuffer mapResult(fromSeq(field))

    implicit def canBuildFrom[FT,ST]:
            CanBuildFrom[FieldSequence[FT,ST], ST, FieldSequence[FT,ST]] =
      new CanBuildFrom[FieldSequence[FT,ST], ST, FieldSequence[FT,ST]] {
        def apply(): Builder[ST,FieldSequence[FT,ST]]
            = newBuilder[FT,ST]( _ ) // What goes here?
        def apply(from: FieldSequence[FT,ST]): Builder[ST,FieldSequence[FT,ST]]
            = from.newBuilder
      }
}

问题是隐式定义的 CanBuildFrom 需要一个不带参数的 apply 方法。但是在这些情况下,这种方法是没有意义的,因为需要一个字段(FT 类型)来构造一个 FieldSequence。事实上,仅仅从 ST 类型的序列构造 FieldSequence 应该是不可能的。我能做的最好的事情是在这里抛出异常吗?

【问题讨论】:

    标签: scala scala-collections


    【解决方案1】:

    那么您的课程不满足成为Seq 的要求,并且像flatMap 这样的方法(以及因此的理解)无法为它工作。

    【讨论】:

    • 我想了这么多,但我仍然面临如何实现这样做的问题。可能是 FieldSequence 上抽象方法的默认值?
    • 你真的需要继承吗?还是组合足够好,有一个包装类来处理 Seq 的特殊初始化?包装器仍然可以实现像Traversable 这样更弱的东西,并委托它。
    • 我不确定您是否会将我的解决方案视为继承或组合。我不知道这些术语如何与 Scala 一起使用,而不是更传统的语言。我认为您遇到了一种拉皮条的模式,但由于我控制了班级,我认为没有必要。
    【解决方案2】:

    我不确定我是否同意 Landei 关于 flatMapmap 的看法。如果替换为抛出这样的异常,大部分操作应该可以工作。

    def apply(): Builder[ST,FieldSequence[FT,ST]] = sys.error("unsupported")
    

    根据我在TraversableLikemapflatMap 中看到的内容,大多数其他人使用apply(repr) 版本。所以对于理解似乎有效。感觉它也应该遵循 Monad 法则(该领域只是跨越)。

    鉴于您拥有的代码,您可以这样做:

    scala> val fs = FieldSequence.fromSeq("str")(Vector(1,2))
    fs: FieldSequence[java.lang.String,Int] = FieldSequence(1, 2)
    
    scala> fs.map(1 + _)
    res3: FieldSequence[java.lang.String,Int] = FieldSequence(2, 3)
    
    scala> val fs2 = FieldSequence.fromSeq("str1")(Vector(10,20))
    fs2: FieldSequence[java.lang.String,Int] = FieldSequence(10, 20)
    
    scala> for (x <- fs if x > 0; y <- fs2) yield (x + y)
    res5: FieldSequence[java.lang.String,Int] = FieldSequence(11, 21, 12, 22)
    

    以下内容不起作用:

    scala> fs.map(_ + "!")
    // does not return a FieldSequence
    
    scala> List(1,2).map(1 + _)(collection.breakOut): FieldSequence[String, Int]
    java.lang.RuntimeException: unsupported
    // this is where the apply() is used
    

    要使breakOut 工作,您需要实现apply() 方法。我怀疑您可以为field:def apply() = newBuilder[FT, ST](getDefault) 生成一个具有某些默认值的构建器,并使用对您的用例有意义的getDefault 实现。

    对于fs.map(_ + "!")不保留类型,你需要修改你的签名和实现,让编译器可以找到CanBuildFrom[FieldSequence[String, Int], String, FieldSequence[String, String]]

    implicit def canBuildFrom[FT,ST_FROM,ST]:
            CanBuildFrom[FieldSequence[FT,ST_FROM], ST, FieldSequence[FT,ST]] =
      new CanBuildFrom[FieldSequence[FT,ST_FROM], ST, FieldSequence[FT,ST]] {
        def apply(): Builder[ST,FieldSequence[FT,ST]]
            = sys.error("unsupported")
        def apply(from: FieldSequence[FT,ST_FROM]): Builder[ST,FieldSequence[FT,ST]]
            = newBuilder[FT, ST](from.field)
      }
    

    【讨论】:

    • 感谢您的意见,它让我思考问题以便得到更好的答案。
    【解决方案3】:

    最后,我的答案与a previous question 中的答案非常相似。该问题与我的原始答案和答案的差异很小,但基本上允许具有序列的任何内容成为序列。

    import scala.collection.SeqLike
    import scala.collection.mutable.Builder
    import scala.collection.mutable.ArrayBuffer
    import scala.collection.generic.CanBuildFrom
    
    trait SeqAdapter[+A, Repr[+X] <: SeqAdapter[X,Repr]]
            extends Seq[A] with SeqLike[A,Repr[A]] {
        val underlyingSeq: Seq[A]
        def create[B](seq: Seq[B]): Repr[B]
    
        def apply(index: Int) = underlyingSeq(index)
        def length = underlyingSeq.length
        def iterator = underlyingSeq.iterator
    
        override protected[this] def newBuilder: Builder[A,Repr[A]] = {
            val sac = new SeqAdapterCompanion[Repr] {
                def createDefault[B](seq: Seq[B]) = create(seq)
            }
            sac.newBuilder(create)
        }
    }
    
    trait SeqAdapterCompanion[Repr[+X] <: SeqAdapter[X,Repr]] {
        def createDefault[A](seq: Seq[A]): Repr[A]
        def fromSeq[A](creator: (Seq[A]) => Repr[A])(seq: Seq[A]) = creator(seq)
        def newBuilder[A](creator: (Seq[A]) => Repr[A]): Builder[A,Repr[A]] =
            new ArrayBuffer mapResult fromSeq(creator)
    
        implicit def canBuildFrom[A,B]: CanBuildFrom[Repr[A],B,Repr[B]] =
            new CanBuildFrom[Repr[A],B,Repr[B]] {
                def apply(): Builder[B,Repr[B]] = newBuilder(createDefault)
                def apply(from: Repr[A]) = newBuilder(from.create)
            }
    }
    

    这解决了 huynhjl 提出的所有问题。对于我最初的问题,要将字段和序列视为序列,现在可以使用一个简单的类。

    trait Field[FT] {
        val defaultValue: FT
    
        class FieldSeq[+ST](val field: FT, val underlyingSeq: Seq[ST] = Vector())
                extends SeqAdapter[ST,FieldSeq] {
            def create[B](seq: Seq[B]) = new FieldSeq[B](field, seq)
        }
    
        object FieldSeq extends SeqAdapterCompanion[FieldSeq] {
            def createDefault[A](seq: Seq[A]): FieldSeq[A] =
                new FieldSeq[A](defaultValue, seq) 
            override implicit def canBuildFrom[A,B] = super.canBuildFrom[A,B]
        }
    }
    

    可以这样测试:

    val StringField = new Field[String] { val defaultValue = "Default Value" }
    StringField: java.lang.Object with Field[String] = $anon$1@57f5de73
    
    val fs = new StringField.FieldSeq[Int]("str", Vector(1,2))
    val fsfield = fs.field
    fs: StringField.FieldSeq[Int] = (1, 2)
    fsfield: String = str
    
    val fm = fs.map(1 + _)
    val fmfield = fm.field
    fm: StringField.FieldSeq[Int] = (2, 3)
    fmfield: String = str
    
    val fs2 = new StringField.FieldSeq[Int]("str1", Vector(10, 20))
    val fs2field = fs2.field
    fs2: StringField.FieldSeq[Int] = (10, 20)
    fs2field: String = str1
    
    val ffor = for (x <- fs if x > 0; y <- fs2) yield (x + y)
    val fforfield = ffor.field
    ffor: StringField.FieldSeq[Int] = (11, 21, 12, 22)
    fforfield: String = str
    
    val smap = fs.map(_ + "!")
    val smapfield = smap.field
    smap: StringField.FieldSeq[String] = (1!, 2!)
    smapfield: String = str
    
    val break = List(1,2).map(1 + _)(collection.breakOut): StringField.FieldSeq[Int]
    val breakfield = break.field
    break: StringField.FieldSeq[Int] = (2, 3)
    breakfield: String = Default Value
    
    val x: StringField.FieldSeq[Any] = fs
    val xfield = x.field
    x: StringField.FieldSeq[Any] = (1, 2)
    xfield: String = str
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-07-08
      • 1970-01-01
      • 2011-05-17
      • 1970-01-01
      • 1970-01-01
      • 2014-11-15
      • 2013-04-23
      相关资源
      最近更新 更多