読者です 読者をやめる 読者になる 読者になる

型パラメータの境界とは

Scala

パラメータ境界

型パラメータには以下の3つの境界が設定できる。

  • 上限境界(upper bound)
    • 例 hoge[A <: T]
    • A が T のサブタイプでなければならない
  • 下限境界(lower bound)
    • 例 hoge[A >: T]
    • A は T のスーパータイプでなければならない
  • 可視境界(view bound)
    • 例 hoge[A <% T]
    • A が T として扱える(暗黙の型変換により変換可能または A が T のサブタイプ)

通常の型パラメータ

普通の型パラメータからおさらい。以下の Holder を考える。

  class Holder[A] {
    private var contents:A = _
    def set(value:A) {contents = value}
    def get:A = contents
  }

以下のように型パラメータとしてIntを指定した場合、当然 Double の 3.14 は格納できない。

  val h = new Holder[Int]
  h.set(100)
  h.set(3.14)  // エラー

型パラメータとして Number を指定した場合、Int も Double も格納できる。当然 String は格納できない。

  val h = new Holder[Number]
  h.set(200)
  h.set(3.14)
  h.set("Foo")  // エラー

上限境界

上限境界は A

  class Hoge[A <: java.io.Closeable]

  val h1 = new Hoge[java.io.InputStream]

関数についても同じように指定も可能。

  def foo[A <: java.io.Closeable](x:A) = {x.close}

  foo(new java.io.FileInputStream("bar"))

close() メソッドを持っているものを受け付ける場合には以下のように書くことが出来る。

  def foo[A <: {def close():Unit}](x:A) = {x.close}

  foo(new java.io.FileInputStream("bar"))
  foo(java.awt.SplashScreen.getSplashScreen())

java.awt.SplashScreen は Closeable インターフェースを実装していないが、close()メソッドを持っており、コンパイル時にclose()メソッドの有無がチェックされる。構造タイプ指定。
ただ、java.awt.SplashScreen の close() メソッドは Closeable インターフェースの close() とは意味合いが異なるため Closeable を実装していない というスタンスに立つと、上記のようなダックタイピング的な扱いは適切ではないかも。


先程の Holder の例で行くと、def set(h:Holder[A]) を追加して、別の Holder から値を設定できるようにしたい。

  class Holder[A] {
    private var contents:A = _
    def set(value:A) {contents = value}
    def set(h:Holder[A]) {contents = h.get}
    def get:A = contents
  }

以下はもちろん同じ型なので上手くいく。

  val h1 = new Holder[Number]
  val h2 = new Holder[Number]

  h1.set(100)
  h2.set(h1)

しかし、以下はコンパイルエラーとなる。

  val hNum = new Holder[Number]
  val hInt = new Holder[Integer]

  hInt.set(100)
  hNum.set(hInt) // エラー

全然柔軟でない。以下のようにHolderを変更することでコンパイルエラーは無くなる。

  class Holder[A] {
    private var contents:A = _
    def set(value:A) {contents = value}
    def set[U<:A](h:Holder[U]) {contents = h.get}
    def get:A = contents
  }

下限境界

下限境界は A >: T のように指定し、A は T のスーパータイプで無ければならないという規定となる。以下のクラスを考える。

  class Cons[A](val head:A, val trailing:List[A]) {
    def append(x:A) = new Cons[A](x, head :: trailing)
    override def toString = (head :: trailing).toString
  }

head という A 型の値と、それに続く A 型のリストを持ち、append メソッドにより値を先頭(head)に追加した新しい Cons オブジェクトを返すメソッドがある(無理やり感あるが・)。このクラスは以下のように使える。

  val c = new Cons(1, 2 :: Nil)
  println(c) // List(1, 2)
  println(c.append(0)) // List(0, 1, 2)

ここで以下のように Double 値や String 値を append しようとするとコンパイルエラーとなる。

  println(c.append(0.5))    // エラー
  println(c.append("foo"))  // もちろんエラー

c は Int で型パラメータ化されてるし当然。
appendを以下のようにすると、

  class Cons[A](val head:A,val trailing:List[A]) {
    def append[U>:A](x:U) = new Cons[U](x, head :: trailing)
    override def toString = (head :: trailing).toString
  }

スーパークラスで再定義されたオブジェクトが得られる。

  println(c.append(0.5))    // List(foo, 1, 2)
  println(c.append("foo"))  // List(0.5, 1, 2)

可視境界

可視境界は A <% T のように指定し、A は T のサブタイプ、または暗黙的型変換で変換可能なものとなり、よりゆるい境界条件となる。