开发者

Extending collection classes with extra fields in Scala

开发者 https://www.devze.com 2023-04-05 21:23 出处:网络
I\'m looking to create a class that is basically a collection with an extra field. However, I keep running into problems and am wondering what the best way of implementing this is. I\'ve tried to foll

I'm looking to create a class that is basically a collection with an extra field. However, I keep running into problems and am wondering what the best way of implementing this is. I've tried to follow the pattern given in the Scala book. 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())
   开发者_Go百科     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
      }
}

The problem is the CanBuildFrom that is implicitly defined needs an apply method with no arguments. But in these circumstances this method is meaningless, as a field (of type FT) is needed to construct a FieldSequence. In fact, it should be impossible to construct a FieldSequence, simply from a sequence of type ST. Is the best I can do to throw an exception here?


Then your class doesn't fulfill the requirements to be a Seq, and methods like flatMap (and hence for-comprehensions) can't work for it.


I'm not sure I agree with Landei about flatMap and map. If you replace with throwing an exception like this, most of the operations should work.

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

From what I can see in TraversableLike, map and flatMap and most other ones use the apply(repr) version. So for comprehensions seemingly work. It also feels like it should follow the Monad laws (the field is just carried accross).

Given the code you have, you can do this:

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)

What doesn't work is the following:

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

For breakOut to work you would need to implement the apply() method. I suspect you could generate a builder with some default value for field: def apply() = newBuilder[FT, ST](getDefault) with some implementation of getDefault that makes sense for your use case.

For the fact that fs.map(_ + "!") does not preserve the type, you need to modify your signature and implementation, so that the compiler can find a 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)
  }


In the end, my answer was very similar to that in a previous question. The difference with that question and my original and the answer are slight but basically allow anything that has a sequence to be a sequence.

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

This fixes all the problems huynhjl brought up. For my original problem, to have a field and a sequence treated as a sequence, a simple class will now do.

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

This can be tested as so:

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
0

精彩评论

暂无评论...
验证码 换一张
取 消