RNA メソッドの戻り値型の適応 トップ 共通する演算の摘出 新しいコレクションの参入 目次

新しいコレクションの参入

定義済みの演算を正しい型で利用できるように新しいコレクションクラスを参入させるには何が必要だろうか。これから数ページにわたって、二つの具体例を見てみよう。

列の参入

 

abstract class Base
case object A extends Base
case object G extends Base
case object C extends Base
case object U extends Base
  
object Base {
  val fromInt: Int => Base = Array(A, G, C, U)
  val toInt: Base => Int = Map(A -> 0, G -> 1, C -> 2, U -> 3)
}
RNA 塩基

 

  import collection.IndexedSeqLike
  import collection.mutable.{BuilderArrayBuffer}
  import collection.generic.CanBuildFrom
  
  final class RNA1 private (val groups: Array[Int],
      val length: Intextends IndexedSeq[Base] {
  
    import RNA1._
  
    def apply(idx: Int): Base = {
      if (idx < 0 || length <= idx)
        throw new IndexOutOfBoundsException
      Base.fromInt(groups(idx / N) >> (idx % N * S) & M)
    }
  }
  
  object RNA1 {
  
    // グループを表すのに必要なビット数
    private val S = 2            
  
    // Int に収まるグループ数
    private val N = 32 / S       
  
    // グループを分離するマスク
    private val M = (1 << S) - 1 
  
    def fromSeq(buf: Seq[Base]): RNA1 = {
      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 RNA1(groups, buf.length)
    }
  
    def apply(bases: Base*) = fromSeq(bases)
  }
RNA鎖クラス、第一版

A(アデニン)、G(グアニン)、C(シトシン)、および U(ウラシル)の塩基の列からなる RNA鎖のための新しい列型を作成したいとする。上記のRNA 塩基のコードに示したように、塩基の定義は簡単なものだ。

全ての塩基は共通の抽象クラス Base を継承するケースオブジェクトとして定義される。Base クラスにはコンパニオンオブジェクトがあり、塩基と 0〜3 の整数を関連付ける二つの関数を定義する。これらの関数ではコレクションを使った関数の実装を二つの異なる方法で例示した。toInt 関数は Base から整数への Map として実装される。逆関数の fromInt は配列として実装された。これは、マップと配列の両方が Function1 を継承する関数であることを利用している。

次に、RNA鎖のためのクラスを定義する。概念的には、RNA鎖は単に Seq[Base] だ。しかし、RNA鎖は非常に長くなる可能性があるため、コンパクトな表現方法を考えてみよう。四つの塩基しかないため、一つの塩基は 2ビットで識別でき、一つの整数内に 16 の塩基を 2ビット値として格納できる。後は、パック化した表現を用いる特殊な Seq[Base] の子クラスを構築すればいいと。

RNA鎖クラスのコードは、この第一版を表す。これは後により洗練されたものに修正される。 RNA1Int 配列を第一引数として取るコンストラクタを持つ。この配列は、各要素に 16の塩基を持つ(ただし、最後の要素は空きが許される)パック化された RNA データを含む。第二引数の length は配列(および列)中の塩基の数を指定する。RNA1 クラスは IndexedSeq[Base] を拡張する。 scala.collection.immutable パッケージの IndexedSeq トレイトは lengthapply の二つの抽象メソッドを定義する。これらは具象子クラス (concrete subclass) 内で実装されなくてはいけない。RNA1 は同名のパラメータフィールドを定義することで自動的に length を実装する。RNA1 クラスのコードで示したように添字メソッドの apply を実装する。基本的には、apply はまず groups 配列から整数値を抽出して、次に右シフト(>>)とマスク(&)を用いて適切な 2ビット値を抽出する。プライベートな定数 SN、および MRNA1 コンパニオンオブジェクトにて定義される。S は各パケットのサイズ(つまり 2)を指定し、N は整数毎の 2ビットパケットの数を指定し、M はワードから下位 S ビットを抽出するためのビットマスクだ。

RNA1 クラスのコンストラクタが private であることに注意してほしい。これは、RNA1 シークエンスがパック化された配列によって表現されていることをクライアントから隠蔽するため、彼らが new を呼び出して RNA1 シークエンスを作成できないことを意味する。クライアントから RNA シークエンスの内部表現が隠されていることで、クライアント側のコードに影響無く内部表現を変更することができる。別の言い方をすると、この設計は RNA シークエンスのインターフェイスと実装の分離 (decoupling) を実現する。しかし、new を用いた RNA シークエンスの作成が不可能な場合、RNA シークエンスを作成する他の方法がなければクラスそのものが役に立たない。RNA1 のコンパニオンオブジェクトは RNA シークエンスの作成のための二つの方法を提供する。第一の方法は、塩基の列(Seq[Base] の値)を RNA1 クラスのインスタンスに変換する fromSeq メソッドだ。fromSeq メソッドは、引数の列に含まれる塩基を配列にパック化した後、その配列と元の列の長さを引数として RNA1 のプライベートなコンストラクタを呼び出す。これは、クラスのコンパニオンオブジェクトからプライベートなコンストラクタが使えることを利用している。

RNA1値を作成する第二の方法は、RNA1 オブジェクトの apply メソッドにより提供される。これは可変数の Base を引数として取り、列として fromSeq に渡す。以下に二つの作成方法の具体例を見てみる:

scala> val xs = List(A, G, C, A)
xs: List[Product with Base] = List(A, G, C, A)
  
scala> RNA1.fromSeq(xs)
res1: RNA1 = RNA1(A, G, C, A)
  
scala> val rna1 = RNA1(A, U, G, G, C)
rna1: RNA1 = RNA1(A, U, G, G, C)

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


RNA メソッドの戻り値型の適応 トップ 共通する演算の摘出 新しいコレクションの参入 目次