How can I guarantee that the dynamic type of my custom Scala collection is preserved during map ()?

I read a very interesting article about Scala 2.8 collection architecture , and I experimented a bit with it. To get started, I just copied the final code for a nice example RNA. Here it is for reference:

abstract class Base
case object A extends Base
case object T extends Base
case object G extends Base
case object U extends Base

object Base {
  val fromInt: Int => Base = Array(A, T, G, U)
  val toInt: Base => Int = Map(A -> 0, T -> 1, G -> 2, U -> 3)
}

final class RNA private (val groups: Array[Int], val length: Int)
    extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA] {

  import RNA._

  // Mandatory re-implementation of `newBuilder` in `IndexedSeq`
  override protected[this] def newBuilder: Builder[Base, RNA] =
    RNA.newBuilder

  // Mandatory implementation of `apply` in `IndexedSeq`
  def apply(idx: Int): Base = {
    if (idx < 0 || length <= idx)
      throw new IndexOutOfBoundsException
    Base.fromInt(groups(idx / N) >> (idx % N * S) & M)
  }

  // Optional re-implementation of foreach, 
  // to make it more efficient.
  override def foreach[U](f: Base => U): Unit = {
    var i = 0
    var b = 0
    while (i < length) {
      b = if (i % N == 0) groups(i / N) else b >>> S
      f(Base.fromInt(b & M))
      i += 1
    }
  }
}

object RNA {

  private val S = 2 // number of bits in group
  private val M = (1 << S) - 1 // bitmask to isolate a group
  private val N = 32 / S // number of groups in an Int

  def fromSeq(buf: Seq[Base]): RNA = {
    val groups = new Array[Int]((buf.length + N - 1) / N)
    for (i <- 0 until buf.length)
      groups(i / N) |= Base.toInt(buf(i)) << (i % N * S)
    new RNA(groups, buf.length)
  }

  def apply(bases: Base*) = fromSeq(bases)

  def newBuilder: Builder[Base, RNA] =
    new ArrayBuffer mapResult fromSeq

  implicit def canBuildFrom: CanBuildFrom[RNA, Base, RNA] =
    new CanBuildFrom[RNA, Base, RNA] {
      def apply(): Builder[Base, RNA] = newBuilder
      def apply(from: RNA): Builder[Base, RNA] = newBuilder
    }
}

Now, here is my problem. If I run this, everything is fine:

val rna = RNA(A, G, T, U)
println(rna.map(e => e)) // prints RNA(A, G, T, U)

but this code converts RNA to vector!

val rna: IndexedSeq[Base] = RNA(A, G, T, U)
println(rna.map(e => e)) // prints Vector(A, G, T, U)

This is a problem, because client code that is not aware of the class RNAcan convert it back to Vector, and not just from Baseto Base. Why is this so, and what are the ways to fix it?

P.-S: I found a preliminary answer (see below), please correct me if I am wrong.

+4
2

rna IndexedSeq[Base], CanBuildFrom rna, , rna rna.

, ? GenericCanBuildFrom, IndexedSeq. GenericCanBuildFrom , genericBuilder[B] , , , B - , , map() .

rna IndexedSeq[Base], IndexedSeq, genericBuilder[B] rna rna - - B Base - , .

, , Vector . , , ...

. map() , A . . . Scala() ?.

+3

, , . ( , ). :

? IndexedSeq [Base], , IndexedSeq [Base], . , IndexedSeq, - , , . ,

filter , . , . map , .

, , . RNA, , , , .

, , val rna: IndexedSeq[Base], rna IndexedSeq. doSomething(rna), , ? def doSomething[T](rna: IndexedSeq[Base]): T def doSomething[T](rna: RNA): T?

, . , , rna rna. , , , , . , , - . , .

, :

val rna = RNA(A, G, T, U)
val rna2 = doSomething(rna)

- :

def doSomething[U](seq: IndexedSeq[U]) = seq.map(identity)

rna2 rna, ... , - , CanBuildFrom, , :

def doSomething[U, To](seq: IndexedSeq[U])
   (implicit cbf: CanBuildFrom[IndexedSeq[U], U, To]) = seq.map(identity)(cbf)

: val rna2: RNA = doSomething(rna)(collection.breakOut)

+1

All Articles