使用Scala中的额外字段扩展集合类

时间:2011-09-20 08:32:01

标签: scala scala-collections

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

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方法。但是在这些情况下,这种方法毫无意义,因为构建FieldSequence需要一个(FT类型)字段。实际上,简单地从ST类型的序列构造FieldSequence应该是不可能的。我能在这里抛出异常是最好的吗?

3 个答案:

答案 0 :(得分:0)

然后,您的课程不符合Seq的要求,flatMap等方法(以及因此的理解)也不适用于此。

答案 1 :(得分:0)

我不确定我同意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)
  }

答案 2 :(得分:0)

最后,我的答案与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