如何确保在map()期间保留自定义Scala集合的动态类型?

| 我阅读了有关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转换为Vector!
val rna: IndexedSeq[Base] = RNA(A, G, T, U)
println(rna.map(e => e)) // prints Vector(A, G, T, U)
这是一个问题,因为不知道
RNA
类的客户端代码可能会将其转换回
Vector
,而不是仅从
Base
映射到
Base
时。为什么会这样,有什么解决方法? 附言:我找到了一个初步的答案(见下文),如果我错了,请纠正我。     
已邀请:
如果
rna
变量的静态类型为
IndexedSeq[Base]
,则自动插入的
CanBuildFrom
不能是
RNA
伴随对象中定义的那个,因为编译器不应该知道
rna
RNA
的实例。 那么它是从哪里来的呢?编译器将回退到
GenericCanBuildFrom
的实例,该实例在
IndexedSeq
对象中定义。
GenericCanBuildFrom
通过在原始集合上调用
genericBuilder[B]
来生成其生成器,并且对该通用生成器的要求是它可以生成可以容纳任何类型
B
的泛型集合-当然,传递给
map()
的函数的返回类型不受限制。 在这种情况下,
RNA
只是
IndexedSeq[Base]
,而不是通用的
IndexedSeq
,因此不可能覆盖
RNA
中的
genericBuilder[B]
以返回特定于
RNA
的构建器—我们将不得不在运行时检查
B
Base
还是其他东西,但是我们不能那样做。 我认为这可以解释为什么我们要退还5英镑。至于我们如何解决它,这是一个悬而未决的问题…… 编辑:修复此问题需要
map()
知道它是否映射到
A
的子类型。为此,需要对馆藏库进行重大更改。查看相关问题Scala的map()在映射到相同类型时是否应该表现出不同?     
关于为什么我认为静态键入比RNA弱的类型不是一个好主意。它实际上应该是一条评论(因为它更像是一条意见,但很难阅读)。从您的评论到我的评论:   为什么不?作为IndexedSeq [Base]的子类,根据Liskov替换原则,RNA能够完成IndexedSeq [Base]的所有工作。有时,您所知道的只是它是一个IndexedSeq,您仍然希望过滤器,地图和好友保持相同的特定实现。实际上,过滤器可以做到-但不是地图
filter
这样做是因为编译器可以静态地保证它。如果保留特定集合中的元素,则最终会得到相同类型的集合。
map
不能保证,这取决于传递的功能。 我的意思更多是关于明确指定类型并期望超出其传递范围的行为。作为RNA收集的用户,我可能编写取决于该收集的某些属性(例如有效的内存表示形式)的代码。 因此,假设我在ѭ33中声明
rna
只是an15ѭ。几行之后,我调用了方法“ 36”,该方法期望有效的内存表示形式,对此最好的签名是什么?
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)
    

要回复问题请先登录注册