如何解决如何确保在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,RNA] {
def apply(): Builder[Base,RNA] = newBuilder
def apply(from: RNA): Builder[Base,RNA] = newBuilder
}
}
现在,这是我的问题。如果运行此命令,一切正常:
val rna = RNA(A,U)
println(rna.map(e => e)) // prints RNA(A,U)
但是这段代码将RNA转换为Vector!
val rna: IndexedSeq[Base] = RNA(A,U)
println(rna.map(e => e)) // prints Vector(A,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)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。