LL脳がscalaの勉強を始めたよ その69
Scalaコップ本の19章を続けてやっていきますよー、今回も前回に引き続き変位指定アノテーションについてです。
ちなみに、前回までに構築したサンプルの待ち行列クラスはこんな感じでしたねー
// 外部からアクセスすための公開インターフェースとしてのトレイト trait Queue[T]{ def head:T def tail:Queue[T] def append(x:T):Queue[T] } // クラス実装自体を格納するオブジェクトです object Queue{ // 待ち行列クラスの実装を行う非公開クラスです // 実装内容はこれまでのQueueと同じですネ private class QueueImpl[T]( private val leading:List[T], private val trailing:List[T] // トレイトミックスインです ) extends Queue[T]{ def mirror = { if(leading.isEmpty) new QueueImpl(trailing.reverse, Nil) else this } def head:T = mirror.leading.head def tail:QueueImpl[T] = { val q = mirror new QueueImpl(q.leading.tail, q.trailing) } def append(x:T) = new QueueImpl(leading, x::trailing) } // オブジェクト生成用のapplyメソッドデス def apply[T](xs:T*):Queue[T] = new QueueImpl[T](xs.toList, Nil) }
変位指定アノテーションのチェック
変位指定アノテーションが問題を起こす場合
変位指定アノテーションが問題を起こす場合として前回までに「再代入可能なフィールド」「配列要素」の2つの例を見てきたのですが、純粋関数行列のような再代入可能フィールドがない場合でも型の問題が起こる場合があるみたいです。
例えば共変を定義したQueueクラスがあるとして、そのサブクラスを次のように定義してやります。
class StrangeIntQueue extends Queue[Int] { override def append(x:Int) = { println(Math.sqrt(x)) super.append(x) } }
上記のようなサブクラスを利用して次のようなコードを書くとInt型にString型を入れてしまうという型の問題が発生するのですな(´・ω・`)
val x:Queue[Any] = new StrangeIntQueue x.append("abc")
共変なのでInt→Any、Any→Stringの流れはOKなものの、結果的にInt→Stringになるので破綻してします…とc⌒っ゚д゚)っφ メモメモ...
...なので、そもそもScalaではメソッドパラメータの型としてジェネリックなパラメータ型がある場合、そのメソッドを所有するクラスやトレイトは型パラメータの共変が行えないようになっておるそうです。そこらへん、しっかりチェックしてるのね(´・ω・`)
とりあえず実際のコンパイラ様の動作を見てみますかね
// ジェネリック型のメソッドappendをもつクラスを // 共変で定義してやりますよ class Queue[+T] { def append(x:T) = {} } // コンパイラ様にダメ条件をはっけんされたので絶賛エラー発生しますな <console>:5: error: covariant type T occurs in contravariant position in type T of value x def append(x:T) = {}
ちなみに、再代入可能なフィールドにはメソッドパラメータ型として+アノテーションの付与された型パラメータを使えない理由としては、再代入可能なフィールドのvar x:Tが定義されるとゲッターdef x:Tとセッターdef x_=(y:T)が暗黙のうちに定義されるためセッターメソッドのフィールド型Tが存在することになるので、上記と同様の理由で共変にできなくなるからだそうです(´・ω・`)