2011-04-14 19 views
3

我讀了非常有趣的article on the architecture of the Scala 2.8 collections,我一直在嘗試一下。首先,我簡單地複製了最好的代碼RNA的例子。這裏僅供參考:如何確保我的自定義Scala集合的動態類型在映射()期間保留?

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代替當僅映射從BaseBase。爲什麼會這樣,以及有什麼方法來解決它?

P.-S .:我找到了一個試探性的答案(見下文),如果我錯了,請糾正我。

+0

這可能是一個愚蠢的想法,但是......你爲什麼,因爲它在IndexedSeq做不覆蓋的伴侶? – CheatEx 2011-04-14 09:40:57

+0

'companion'必須返回'GenericCompanion [IndexedSeq]'。如果'RNA'對象實現它,那麼它必須定義'def newBuilder:Builder [A,IndexedSeq [A]]',它比我們想提供的構建器更普遍,所以我們不能這樣做。 – 2011-04-14 11:56:08

+0

類似於http://stackoverflow.com/questions/4563484/different-behavior-when-declaration-type-is-differentset-vs-treeset/4564811#4564811。我也認爲客戶端代碼顯式輸入到'IndexedSeq [Base]'並不是一個好主意,但是卻期望RNA行爲。爲什麼不做'val rna = RNA(A,G,T,U)'? – huynhjl 2011-04-14 12:03:37

回答

3

如果靜態類型rna變量是IndexedSeq[Base],自動插入CanBuildFrom不能在RNA同伴對象中定義的一個,因爲編譯器不應該知道rnaRNA一個實例。

那麼它從哪裏來?編譯器回退到GenericCanBuildFrom的一個實例,該實例在IndexedSeq對象中定義。 GenericCanBuildFrom s通過在原始集合上調用genericBuilder[B]來生成其構建者,並且該通用構建器的要求是它可以生成可以保存任何類型的泛型集合B - 當然,傳遞給map()的函數的返回類型不是限制。

在這種情況下,RNA只是IndexedSeq[Base],而不是一個普通的IndexedSeq,所以它不可能覆蓋genericBuilder[B]RNA返回一個RNA特異性建設者 - 我們會檢查在運行時BBase或別的東西, ,但我們不能那樣做。

我想這解釋了爲什麼,在問題中,我們得到一個Vector回來。至於如何能解決這個問題,它是一個開放性的問題......

編輯:修復這需要map()知道它是否是映射的A與否亞型。爲了實現這種情況,收藏庫中將發生重大變化。請參閱相關問題Should Scala's map() behave differently when mapping to the same type?

1

關於爲什麼我認爲靜態鍵入比RNA更弱的類型並不是一個好主意。它應該是一個評論(因爲它更多的意見,但這將是更難以閱讀)。從你的評論到我的評論:

爲什麼不呢?作爲IndexedSeq [Base]的子類,按照Liskov替換原則,RNA能夠完成IndexedSeq [Base]所做的一切。有時候,你只知道它是一個IndexedSeq,你仍然期望過濾器,地圖和朋友保持相同的具體實現。其實,過濾器做它 - 但不是映射

filter做它,因爲編譯器可以靜態保證。如果您保留特定集合中的元素,則最終會得到相同類型的集合。 map不能保證,它取決於傳遞的函數。

我的觀點更多的是在的行爲中明確指出了一種類型,並期待比它能提供的更多。作爲RNA集合的用戶,我可以編寫依賴於該集合的某些屬性的代碼,例如有效的內存表示。

因此,讓我們假設我在val rna: IndexedSeq[Base]指出rna只是IndexedSeq。幾行後,我打電話給方法doSomething(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) 

我想有rna2RNA對象,但是這不會發生。 ..這意味着這個人應該寫一個方法,需要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)

+0

非常感謝您的詳細解釋。我大多同意。我只是覺得很可惜,在這種特殊情況下,集合庫其他部分存在的一致性會被打破。 「地圖」真的不可能被定義爲我們可以像過濾器一樣保存相同類型的集合?也許像'def smarterMap [B](f:(A)⇒B)(implicit sameTypeEv:A =:= B = null)',其中'sameTypeEv'不爲空,如果我們映射到相同的類型?...只是想出大聲...... – 2011-04-14 13:16:08

+0

或者說:'(隱含的canUseCalleeBuilderEvidence:B <: 2011-04-17 18:26:17

相關問題