2014年5月26日月曜日

Programming in Scala, First Edition: Chapter 20

20. Abstract Members
  classやtraitのmemberは完全な定義がなければabstractになる。
  abstractはsubclassで実装される事を意図している。
  method, field(val, var), type がabstract memberになりえる。
    javaはmethodのみだがscalaではより一般的に概念に拡張されている。
  pre-initialized fields, lazy vals, path-dependent types, enumerationsも扱う。

20.1 A quick tour of abstract members
  method, val, var typeがabstract memberになる。
  trait Abstract {
    type T
    def transform(x: T): T
    val initial: T
    var current: T
  }
  subclassで実装する。
  class Concrete extends Abstract {
    type T = String
    def transform(x: String) = x + x
    val initial = "hi"
    var current = initial
  }
  ?? x: Stringは x: Tのままでも良いのでは? 文法上は可能。

20.2 Type members
  type T のみで定義がない場合はabstract type
  type memberの使用目的
    より短い、分かりやすい名前にするため。
    abstractにしてsubclassで定義させるため。この後のSectionで説明。

20.3 Abstract vals
  何らかの固定値を使いたいが、具体的な値はsubclass毎に決めたい場合に使う。
    名前と型のみ定義する。
     val v: T
  abstract defはvalでoverride可能。
    trait TR { def f: String }
    trait TR_ok extends TR { val f = "a" } // OK
  abstract valはdefでoverride不可能。
    trait TR { val f: String }
    trait TR_ng extends TR { def f = "a" } // NG!!
    valなら値は不変だが、defだと状況に応じて戻り値が変化する可能性があるから。
    これも置き換え(substitution)可能かどうかと言う事。
      valはdefの特殊な形(sub xxx的な発想)と考えることが出来る。
      ** 使う能力(covariant)、受け入れる能力(contravariant)
      ** 特殊化(covariant)、一般化(contravariant)

20.4 Abstract vars
  何らかの可変値を使いたいが、初期値はsubclass毎に決めたい場合に使う。
    名前と型のみ定義する。
      var v: T
  内部的にはgetterとsetterのmethodが定義されるのはabstractでも同じ。
    def v: T
    def v_=(x: T)
    getterとsetterは実装のないabstract methodになる。
    abstractの場合は内部的に値を保持するためのprivate varは定義されない。
      内部的に値を保持するためのprivate varはsubclassで定義される。

20.5 Initializing abstract vals
  traitではparameterを渡せるconstructorを持っていないのでabstractでパラメータ化する。
   trait RationalTrait {
    val numerArg: Int
    val denomArg: Int
  }
  new RationalTrait {
    val numerArg = expr1
    val denomArg = expr2
  }
  traitに実装を定義してnewすると、anonymous classが生成されてnewされる。
    ** javaのinner classと同様。
  new Rational(expr1, expr2)とほぼ同等だが、処理順序が違う。
  classをnewした場合はRationalの初期化の前のexprは評価される。
  traitをnewした場合はexprの評価はanonymous classの初期化の一部として行われる。
    anonymous classの初期化はtraitの初期化の後に行われる。
    traitの初期化でnumerArg、denomArgの値が使えない。
      厳密には初期化前type Intのdefault valueの0になってしまう!!
  Pre-initialized fields
    new {
      val numerArg = expr1
      val denomArg = expr2
    } with RationalTrait
    object twoThirds extends {
      val numerArg = 2
      val denomArg = 3
    } with RationalTrait
    class RationalClass(n: Int, d: Int) extends {
      val numerArg = n
      val denomArg = d
    } with RationalTrait { ... }
    subclassのfieldの初期化をsuperclassの初期化前に行える。
    named classやobjectについても使える。
    初期化前のsuperclassについては参照出来ない。
      thisはsubclassを定義している外側のclassのobjectが参照される。
    ?? 使用上の制約がtraitのsignatureからは読み取れないので分かり難い??
  Lazy vals
    lazy val x = expr
      右辺の評価(初期化)を最初に使われた時のみに出来る。
      defと違うのは最初の一回しか評価されない所。
    object自体もlazy valの様に使用される最初に評価される。
    defとby-name parameterは使用されるたびに評価される。
    副作用と関係ない場合に使える。
      副作用があると順番が把握しにくいので使いにくい。
      命令型だと使いにくいと言う事。
      関数型に向いている。処理の順序に依存しないから。
  Lazy functional languages
    遅延評価(lazy evaluation)で有名なのはHaskell。

20.6 Abstract types
  abstract class A { type T; ... }
  aliasの実体のないtype宣言がabstract typeとなる。
  型の実体はsubclassで定義する。
  abstract type Tはsuperclass内の他の部分でも使える。

20.7 Path-dependent types
  class C { type T = Int }
  val c new C
    c.TはPath-dependent type。
    Tはobject cの型メンバ。objectが異なればTも異なる。
  class Outer { class Inner }
    Outer#Innerで参照出来る。
    class InnerをnewするにはOuterのインスタンス(object)が必要。
      InnerからOuterのインスタンスを参照出来る。
      Outer#Innerはnew出来ない。
    Outerのインスタンス(object)を生成してPath-dependent type指定でnewする。
      val o = new Outer; new o.Inner
      class Innerも型なのでPath-dependent type指定になる。

20.8 Enumerations
  scalaのenumrationsはstandard libraryのclass。
    javaやC#等の他の言語ではbuilt-inの特別なsyntaxが必要。
  Enumerationをextendsすれば定義出来る。
    object Color extends Enumeration {
      val Red, Green, Blue = Value
    }
  inner classにColor.Valueが定義される。
    Red, Green, BlueのtypeはColor.Value
  Color.Valueはpath-dependent type
    object毎に違うtypeになる。
  各値に名前をを結び付けることも出来る。
    object Direction extends Enumeration {
      val North = Value("North")
      val East = Value("East")
      val South = Value("South")
      val West = Value("West")
    }
  foreach, map, flatMap, filterと一緒に使える。
    scala> for (d <- Direction) print(d +" ")
    North East South West
  id methodで0始まりの番号が取れる。
    scala> Direction.East.id
    res5: Int = 1
  idから値を取り出すことも可能。
    scala> Direction(1)
    res6: Direction.Value = East
  更に詳しい内容は Scaladoc comments of class scala.Enumeration 参照。

20.9 Case study: Currencies
  abstract typeはnewできない。他のtypeのsuper typeにもなれない。
    factory methodを使って生成すればよい。
    factory methodをabstractにして、subclassで実装する。
  abstract typeを使えばconcrete typeが決まっていなくてもconcrete methodを定義出来る。
  inner class Inner内で他のOuter内のInnerを指定するときは、Outer#Inner
  別々のtypeを定義して不可能な演算をcompile時点で抑制することでbugを減らせる。
    The crash of the Mars Climate Orbiter spacecraft on September 23, 1999

20.10 Conclusion
  class設計時点で不明なmemberは全てabstract memberにすれば良い。
    type systemが推論してくれる。
  scalaではmethodだけではなくて、type, value, variable全てabstractに出来る。

0 件のコメント:

コメントを投稿