4

我阅读了关于 Scala 2.8 集合体系结构的非常有趣的文章,并且一直在尝试使用它。首先,我只是简单地复制了这个好RNA例子的最终代码。这里供参考:

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

现在,这是我的问题。如果我运行它,一切都很好:

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

但是这段代码将 RNA 转换为向量!

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

这是一个问题,因为不知道该类的客户端代码RNA可能会将其转换回 aVector而不是仅映射 from Baseto Base。为什么会这样,有什么方法可以解决它?

P.-S.:我找到了一个暂定的答案(见下文),如果我错了,请纠正我。

4

2 回答 2

3

如果变量的静态类型rnaIndexedSeq[Base],则自动插入的变量CanBuildFrom不能是RNA伴随对象中定义的变量,因为编译器不应该知道它rna是 的实例RNA

那么它是从哪里来的呢?编译器依赖 的实例GenericCanBuildFrom,即对象中定义的IndexedSeq实例。GenericCanBuildFroms 通过调用genericBuilder[B]原始集合来生成它们的构建器,并且对于该通用构建器的要求是它可以生成可以保存任何类型B的通用集合 - 当然,传递给 a 的函数的返回类型map()不受限制。

在这种情况下,RNAis only anIndexedSeq[Base]而不是 generic IndexedSeq,因此不可能覆盖genericBuilder[B]inRNA以返回RNA-specific 构建器——我们必须在运行时检查BisBase或其他东西,但我们不能这样做。

我认为这解释了为什么在这个问题中,我们得到了Vector支持。至于我们如何解决它,这是一个悬而未决的问题……</p>

编辑:解决这个问题需要map()知道它是否映射到一个子类型A。要做到这一点,需要对馆藏库进行重大更改。请参阅相关问题Scala 的 map() 映射到相同类型时是否应该表现不同?.

于 2011-04-14T08:46:37.163 回答
1

关于为什么我认为静态类型比 RNA 更弱的类型不是一个好主意。它应该是真正的评论(因为它更像是一种观点,但更难阅读)。从你的评论到我的评论:

为什么不?作为 IndexedSeq[Base] 的一个子类,根据 Liskov 替换原则,RNA 能够完成 IndexedSeq[Base] 所做的一切。有时,你只知道它是一个 IndexedSeq,你仍然希望 filter、map 和friends 保持相同的具体实现。实际上,过滤器可以做到 - 但不是映射

filter这样做是因为编译器可以静态保证它。如果您保留来自特定集合的元素,您最终会得到来自同一类型的集合。map不能保证,它取决于传递的函数。

我的观点更多的是明确指定类型并期望比它可以提供的更多的行为。作为 RNA 集合的用户,我可能会编写依赖于该集合的某些属性的代码,例如有效的内存表示。

所以让我们假设我val rna: IndexedSeq[Base]rna只是一个. 几行之后,我调用了一个我期望有效内存表示的方法,最好的签名是什么?还是?IndexedSeqdoSomething(rna)def doSomething[T](rna: IndexedSeq[Base]): Tdef 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)

于 2011-04-14T13:09:03.143 回答