map 類の取り扱い トップ 新しいコレクションの参入 RNA メソッドの戻り値型の適応 目次

RNA メソッドの戻り値型の適応

以下に RNA1 を使った具体例を示す:

scala> rna1.length
res2: Int = 5
  
scala> rna1.last
res3: Base = C
  
scala> rna1.take(3)
res4: IndexedSeq[Base] = Vector(A, U, G)

最初の二つの結果は予想通りかもしれないが、最後の rna1 から先頭の三つの要素を take した結果は予想外だったかもしれない。事実、戻り値は静的型として IndexedSeq[Base] をとり、動的型として Vector をとる。RNA1 値を期待していたかもしれない。しかし、RNA1 クラスのコードで行ったのは RNA1IndexedSeq から継承しただけだったので、それは不可能だ。一方 IndexedSeq クラスは IndexedSeq を返す take メソッドを持ち、 IndexedSeq のデフォルトの実装は Vector だ。 上記のやりとりの最後の行に出てくるベクトルはここからきている。

 

  final class RNA2 private (
    val groups: Array[Int],
    val length: Int
  ) extends IndexedSeq[Base] with IndexedSeqLike[Base, RNA2] {
  
    import RNA2._
  
    override def newBuilder: Builder[Base, RNA2] = 
      new ArrayBuffer[Base] mapResult fromSeq
  
    def apply(idx: Int): Base = // 前と同じ
  }
RNA鎖クラス、第二版

現状の把握ができた所で、これを解決するために何を変更するべきかというのが次の問題だろう。一つの方法としては、RNA1 クラスの take メソッドをオーバーライドしてしまうというのがある:

def take(count: Int): RNA1 = RNA1.fromSeq(super.take(count))
これは take に関しては解決する。しかし、dropfilterinit に関してはどうだろう。 事実、列には 50以上の列を再び返すメソッドがある。一貫性を持たせるためには、これら全てをオーバーライドする必要がある。この方法の魅力がどんどん色あせていくのが分かるだろう。幸い、同じ効果がある別のもっと簡単な方法がある。RNA クラスは、IndexedSeq だけでなく、その実装トレイトである IndexedSeqLike も継承すればいいのだ。これは、上記の RNA2 クラスのコードに示した。この新しい実装は、以前のものと比べて二つの側面においてしか違わない。第一に、RNA2 クラスは IndexedSeqLike[Base, RNA2] を継承する。IndexedSeqLike トレイトは、IndexedSeq の全ての具象メソッド (concrete method) を拡張可能な方法で実装する。例えば、takedropfilter、や init のようなメソッドの戻り値の型は IndexedSeqLike クラスに渡された第二の型パラメータ、つまり RNA2 自身となる。

これを実現するために IndexedSeqLike 自身が、正しい種類のビルダを作成する newBuilder 抽象体に基づいている。IndexedSeqLike トレイトの子クラスは自身の種類のコレクションを返すために newBuilder をオーバーライドしなくてはいけない。RNA2 クラスでは newBuilder メソッドは Builder[Base, RNA2] 型のビルダを返す。

このビルダを構築するために、まず自身が Builder[Base, ArrayBuffer] でもある ArrayBuffer が作成される。次に、ArrayBuffer ビルダを mapResult を呼び出すことで RNA2 ビルダに変換する。mapResult メソッドは、ArrayBuffer から RNA2 への変換関数を引数として期待する。渡された関数は、任意の塩基列から RNA2 に変換する RNA2.fromSeq だ(配列バッファは列の一種なので、RNA2.fromSeq を適用できる)。

もし newBuilder を定義しなかったとすると、以下のようなエラーが発生しただろう:

RNA2.scala:5: error: overriding method newBuilder in trait
TraversableLike of type => scala.collection.mutable.Builder[Base,RNA2];
 method newBuilder in trait GenericTraversableTemplate of type
 => scala.collection.mutable.Builder[Base,IndexedSeq[Base]] has
 incompatible type
class RNA2 private (val groups: Array[Int], val length: Int) 
      ^
one error found

込み入ったコレクションライブラリの仕組みを反映して、このエラーメッセージも非常に長く複雑なものになっている。メソッドの由来に関する情報は、この場合は、役に立つというよりは問題の核心から注意を逸らすことになるので無視することにする。残りは、戻り値型が Builder[Base, RNA2] である newBuilder メソッドが定義される必要があるが、戻り値型が Builder[Base,IndexedSeq[Base]] のメソッド newBuilder ということだ。後者は前者をオーバーライドしない。戻り値型が Builder[Base, RNA2] の最初のメソッドは、RNA2 クラスのコードにおいて、IndexedSeqLike の型パラメータに RNA2 が渡されたことによってこの型でインスタンス化された抽象メソッドだ。戻り値型が Builder[Base,IndexedSeq[Base]] の二番目のメソッドは、継承した IndexedSeq クラスによって提供されているものだ。別の言い方をすると、最初の戻り値型の newBuilder 無しでは RNA2 は無効ということだ。

RNA2 クラスのより洗練された実装により、takedropfilter などのメソッドは期待通り作動する:

scala> val rna2 = RNA2(A, U, G, G, T)
rna2: RNA2 = RNA2(A, U, G, G, T)
  
scala> rna2 take 3
res5: RNA2 = RNA2(A, U, G)
  
scala> rna2 filter (U !=)
res6: RNA2 = RNA2(A, G, G, T)

続いては、map 類の取り扱い


map 類の取り扱い トップ 新しいコレクションの参入 RNA メソッドの戻り値型の適応 目次