2014年4月19日土曜日

Programming in Scala, First Edition Chapter 12

12. Traits
  traitはscalaのコード再利用の基礎単位。
  メソッドとフィールド定義をカプセル化する。
    クラスとmixingすることで再利用できる。
  クラスは複数のtratをmix-inできる。
    scalaでは継承は一つのスーパークラスからのみ。
      他の言語では多重継承をサポートしているものもある。C++等
  二つの代表的な利用方法
    widening thin interfaces
    stackable modifications

12.1 How traits work
  traitの定義
    キーワードclassのtraitを使う。他はクラス定義と同じ。
      trait TRAITNAME extends SUPERCLASSNAME { ... }
    class parameterは使えない。Section 20.5
    fields, concrete methodsも定義できる。
      javaのインタフェースと異なる。
  スーパークラスを指定しない場合はAnyRefを継承する。クラスと同じ。
  クラスにmix-inして使用
    キーワードextendsを使う
      class CLASSNAME(...) extends TRAITNAME { ... }
    キーワードwithを使う
      class CLASSNAME(...) extends SUPERCLASSNAME with TRAIT1NAME with TRAIT2NAME ... { ... }
      withを複数使って複数のtraitをmix-in出来る。
  多重継承とは異なるので、継承ではなくてmix-inと言う。
  super callがdynamically bound => Section 12.6
    クラスではstatically bound
    stackable modificationsが出来るようになる。Section 12.5

12.2 Thin versus rich interfaces
  traitの主な使用方法の一つはクラスの持っているメソッドに自動的にメソッドを加える事。
  richにするかthinにするかはオブジェクト指向設計によくあるtrade-off
    richにすると使うときは便利
  javaのインタフェースはthinになる傾向がある。
    そのインタフェースを実装する人は皆、沢山のメソッドの実装を要求される。
  scalaのtraitなら具象メソッドを実装出来るのでそれを再利用できる。
    scalaのtairtはrichになる傾向がある。
    traitをクラスにmix-inして少ない部分を実装するだけでrichなクラスを作れる。

12.3 Example: Rectangular objects
  四角を表す色々なクラスを作る時に、基本部分をtraitにして再利用する。
  traitに具象メソッドを実装することで、クラスの実装を減らしてrichAPIに出来る。

12.4 The Ordered trait
  trait Ordered[T] {
    def compare(that: T): Int

    def <(that: T): Boolean = (this compare that) < 0
    def >(that: T): Boolean = (this compare that) > 0
    def <=(that: T): Boolean = (this compare that) <= 0
    def >=(that: T): Boolean = (this compare that) >= 0
  }
  class Rational(n: Int, d: Int) extends Ordered[Rational] {
    // ...
    def compare(that: Rational) =
      (this.numer * that.denom) - (that.numer * this.denom)
  }
  RationalにOrdered traitをmix-inする。
  少ない抽象メソッドをクラス内で実装して、それをtraitの具象メソッドが利用する。
    trait内で実装されている具象メソッドの動作を抽象メソッドでカスタマイズする。
  []は型パラメータ
  def compare()はtraitのabstract method
    これを実装すると<,>,<=,>=の比較演算子が使えるようになる。
      演算子は具象クラスで実装されているのでRationalでは実装不要。
  equalsはtrait内では定義出来ない。Chapter 28
    渡された引数の型チェックが必要なため。
      downcastが必要になってしまう。

12.5 Traits as stackable modifications
  trait Doubling extends IntQueue {
    abstract override def put(x: Int) { super.put(2 * x) }
  }
  あるスーパークラスを継承したtraitは同じスーパークラスのサブクラスにしかmix-inできない。
  trait内からスーパークラスの実装されていないabstract methodを呼べる。
    traitのメッソッド定義にabstractキーワードをつける。
    普通のクラスでは出来ない。
    traitは動的束縛で実行時に実装があれば良いから可能。
    他のtraitやクラスが実装を定義した後でmix-inすればOK。
      ?? この後と言うのはどういうタイミング? メソッドが実際に呼ばれる前??
  あるクラスとtraitをmix-inしただけの、特別な実装がないクラスも作れる
    class MyQueue extends BasicIntQueue with Doubling
  クラス名を付けずに直接newでインスタンスを作成する事も出来る。
    val x = new BasicIntQueue with Doubling
  複数のtraitをstack modificationする事も出来る。
    val queue = new BasicIntQueue with Incrementing with Filtering
    右のtraitから実行される。順番の詳細は後で。
  ** 複数traitを連結してパイプや合成関数的な多段変換的な事が出来る。

12.6 Why not multiple inheritance?
  traitと他の言語で良くある多重継承の違いは線形化(linearization)
  線形化することでスーパークラス、traitへのメソッド呼び出し順番が決まる。
    各クラス、traitのメソッドが一回ずつ呼ばれるように線形化される。
    これがstacking of modificationsを可能にしている。
    ダイヤモンド継承問題も発生しない。
  多重継承だと呼び出しはサブクラスからスーパークラスの順に行われる。
    スーパークラスが二つあった場合にどちらを呼ぶか?
      これは実装依存だが、両方を一度に呼ぶことは出来ない。
    それぞれを分けて呼び出した場合により上位のクラスが2重に呼び出されるかも。
      ダイヤモンド継承になっている場合。
  traitでは各クラス、traitのメソッドが一回ずつ呼ばれるように線形化される。
    正確な順番は言語仕様書に記載あるが、少し複雑。
    クラスがtrait、スーパークラス、より先に一番最初に呼ばれる。
    最後に呼ばれるのはスーパークラスと更に上位のtrait, クラス。
    その前は左のtrait順に、そのtraitと上位のtrait, クラスが順に呼ばれる。
     深さ優先探索と同じような順番
    共通の親がいる場合は、マージされて一番遅い順番の所で一回だけ呼ばれる。
  定義例
    class Animal
    trait Furry extends Animal
    trait HasLegs extends Animal
    trait FourLegged extends HasLegs
    class Cat extends Animal with Furry with FourLegged
  呼び出し順
    最後  Animal -> AnyRef -> Any
    3番目 Furry -> (Animal)マージ
    2番目 FourLegged -> HasLegs -> (Animal)マージ
    1番目 Cat
    Cat -> FourLegged -> HasLegs -> Furry -> AnyRef -> Any

12.7 To trait, or not to trait?
  traitと抽象クラスどちらを使うべきか?
    明確な基準は無いが、ガイドラインを示す。
  振る舞いが再利用されない場合は具象クラスにする。
    ** 再利用されないなら抽象化する必要なし。
  クラスと無関係に複数回再利用される場合はtraitにする。
    クラス階層と違う部分いmix-in出来るのはtraitだけ。
    ??なんで??
  javaのコードで使いたい場合は抽象クラスにする。
    実装を持たないtraitはjavaのinterfaceに変換されるので使える。
    scalaの継承はjavaの継承と同じ。
  コンパイル後のバイナリで配布する場合は抽象クラスにする。
    traitのメンバが増減した時は、継承しているクラスは変更なくても再コンパイル必要。
    継承せずに単に機能を呼び出すだけ(使用)にすれば問題なく使える。
  性能が重要なら具象クラスにする。
    JVMはインタフェースよりクラスの方が実行速度が速い。
    traitはインタフェースになるのでクラスにした方が速い。
    ただし、性能の原因がそこにあると確信があるとのみにすべき。
  それでもわからなかったら、まずはtraitにしてみる。
    後から何時でも変えられる。
    一般的にはtraitが一番汎用性が高い。

12.8 Conclusion
  線形化によるsuper callによって多重継承の問題を解決できる。
  stack behaviorが出来る。
  traitは継承を用いたコード再利用の基礎単位。
  この性質から熟練したscalaプログラマーはtraitから書き始める。
  traitは単なるコンセプトの断片に過ぎない
  設計が固まるにつれ、traitのmix-inを通して断片の結合が完全コンセプトを作る。

0 件のコメント:

コメントを投稿