LL脳がscalaの勉強を始めたよ その72


なかなか終わらないScalaコップ本の19章を進めていきますよ、19章は型パラメータについてですねー。ほんと頑張って早く終わらせたい…デス(´・ω・`)

反変

これまで見てきたサンプルは共変か非変のモノだったのですが、反変が自然な場合もあるYO!ってことでそんなサンプルを見ていきますよ。

とりあえず反変のサンプルを

下記コードでは型パラメータTを反変として定義しているので、AnyRefの出力チャネルがStringの出力チャネルのサブ型になる???(´・ω・`)???さっぱりわからないっすね

trait OutputChannel[-T]{
  def write(x:T)
}
とりあえずそんなふうになる理由を見ていきますかねー

サポートされている操作はStringの書き込みなので、OutputChannel[AnyRef]に置き換えてもOutputChannel[AnyRef]にStringの書き込みは可能なので問題はないみたいデスね。

ただし逆にOuyputChannel[AnyRef]をOuyputChannel[String]に置き換えてみると、String以外の書き込みが来る可能性があるのでダメダメ…と(´・ω・`)うん、流れはわかったきふぁする

リスコフ置換原則

上記説明は、U型が必要とされる全ての場所でT型が使えるならT型はU型のサブ型だという、片システム設計の一般であるリスコフ置換原則に則っているみたいですネ(´・ω・`)

…でも、これを言い換えたっぽい「TがUと同じ操作をサポートして、さらにTの全ての操作がUの対応する操作への要求よりも少なく、かつ提供することが多いのであればこの原則は成り立ってる」という表現は…いまいちわからんすな(´・ω・`)とりあえずサブセット的な扱いだからオールOK!みたいなノリでOK?ちなみに要求が少ないというのはAnyRefだったらなんでも良いけどStringだとそのチェックが必要だから要求多いよね、みたいな基準らしいデス

共変と反変が混在している場合

Scalaの関数トレイトのように同じ型が共変と反変の両方を混在するパターンもあるらしいデス。例えばA => Bみたいな関数型ではScala自身がFunction1[A,B]と展開するらしいのですが、この標準ライブラリーのFunction1の定義では共変と反変の両方を使っているとのコト。

ちなみにFunction1はこんな感じみたいですな。ちなみに引数型のSは反変で戻り型のTYが共変なのですが、引数は必要とされるものに対して結果値は与えられるものなのでリスコフ置換原則を満足させるとのことデス。

trait Function1[-S, +T]{
  def apply(x:S):T
}

うーん、引数と戻りの扱いの違いがいまいち分からんすなぁ(´・ω・`)

もういっちょサンプル見てみますかね

図書館での振る舞いを表現したサンプルで変位指定を見てみますかね。とりあえずサンプルはこんな感じです(´・ω・`)

// 出版物を表現するクラスです
// タイトルをパラメータとして取りますね
class Publication(val title: String)
// 本を表現するクラスです
// タイトルをパラメータとして取りますね
class Book(title:String) extends Publication(title)

// 図書館オブジェクトの定義ですね
object Library {
  // 蔵書を定義してやります
  val books:Set[Book] = {
    Set(
      new Book("コップ本"),
      new Book("リフト本")
    )
  }
  // 蔵書一覧を表示しますね
  // パラメータとして関数型Function1をとっておりますな
  def printBookList(info:Book => AnyRef){
    for(book <- books) println(info(book))
  }
}

// 履正社の振る舞いですね
object Customer extends Application {
  // タイトルを取得します
  def getTitle(p:Publication):String = p.title
  // 本の一覧を取得しますね
  Library.printBookList(getTitle)
}

ここでLibraryのprintBookListメソッドは、info:Book => AnyRefをパラメータにとり、結果型がAnyRefのサブ型という構造にもかかわらず型チェックを通過するみたいです。これは
info:Book => AnyRefがFunction1に展開されるので結果型が共変として処理されるらしいです。またprintBookListは蔵書1冊ごとにinfo関数を呼び出してprintln処理(toString)されるのでString以外のAnyRef型のサブクラスであればなんでもOKとのことです。

ついでにprintBoolListメソッドに渡されているinfo関数のパラメータ型がBookと宣言されているけども、Customerの利用時にはBookのスーパークラスであるPublicationをパラメータ型としたgetTitleからの値を利用しているため、Publicationクラスのサブクラスでないと動かないはず…でもBookはPublicationのサブクラスだから問題ナッシング(´・ω・`)というコトみたいです。「関数のパラメータ型の反変が意味するのはそういうこと」と書いてあるけども反変だから継承関係が逆になるってことでいいのかしらね?

とりあえずパラメータ型は反変なのでBook → Publicationになって、結果型は共変なので AnyRef → Stringになる…まとめてみるとBook => AnyRef → Publication => Stringになるので上記のコードはコンパイラ通過時の型チェックを突破できるとか…うーん、一つ一つの要素はなんとなくわかるんだけど、全体的に整理できていないなぁ(´・ω・`)やっぱり時間おいてやり直す必要がありそうですな

いじょー

とりあえず時間切れでここまでです。今週中には19章終わりたいな、終われるかな…が、頑張ります(´・ω・`)