Scalaの抽象メンバー

3928 ワード

Key Words: abstract member , pre-initialized fields , lazy vals , path-dependent types , enumerations
抽象メンバー(Abstract Members)
クラスまたは特質のメンバーが抽象的であるかどうかは、主にそのメンバーがクラスまたは特質で宣言され、サブクラスによって実現される完全な定義抽象メンバーがあるかどうかにかかっている.
抽象メソッド、Scalaに加えて、クラスまたは特質のメンバーとして抽象フィールド、さらには抽象タイプ(types)を宣言することもできます.
Scalaでは、以下に示すように、4つのメンバーの抽象、valsvarsmethodstypesがあります.
trait Abstract {
  type T
  def transform(x: T): T
  val initial: T
  var current: T
}
typeキーワードを使用して抽象タイプを宣言します.このタイプには正確な定義が指定されていません.
Scalaでは抽象クラスも特質も抽象タイプとは呼ばれない.抽象タイプは常に抽象クラスまたは特質のメンバーとして存在し、抽象メンバーは特定のクラス(Concrete)において の形式として存在する.
抽象型の2つの用途:1.冗長なクラス名の簡略化方法2を提供する.宣言された抽象タイプは、サブクラスで定義される必要があります.
抽象不変値(vals)
抽象不変値(abstract val)は、その論理的実装を制約します.不変値(val)のみで定義でき、varまたはdefで定義できません.
抽象メソッド(パラメータなし)はdefまたはvalで定義できます
抽象不変値の初期化
不変値初期化の理解では、クラスパラメータとしてクラス初期化前に評価されることを覚えておく必要があります.特質の抽象不変値として,通常の匿名クラスの初期化方式では,特質の初期化後に評価され,関連する問題を引き起こす可能性がある.対応策は (pre-initialized fields) (lazy vals)の2つです
trait RationalTrait {
  val numerArg: Int
  val denomArg: Int 
}

class Rational(numer: Int, denom: Int)

//   RationalTrait         expr1,expr2     
new RationalTrait {
  val numerArg = expr1
  val denomArg = expr2
}

//   Ratinal       expr1,expr2    
new Rational(expr1, expr2)

特質における抽象不変値の初期化順序がもたらす可能性のある問題に鑑み、 を用いて初期化順序を正確に制御するか、 を用いて遅延処理フィールドの初期化を行う
//     
new {
  val numerArg = expr1
  val denomArg = expr2
} with RationalTrait

//     
trait RatinalTrait {
  lazy val numerArg: Int
  lazy val denomArg: Int
}

new RationalTrait {
  val numerArg = expr1
  val denomArg = expr2
}

抽象可変値(vars)
抽象可変値には特別なところがある.クラスにvarメンバーを定義すると、コンパイラは自動的にそのメンバーにgetterメソッドとsetterメソッドを追加します.抽象的なvarを宣言すると、コンパイラはメンバーにgetterとsetterを暗黙的に追加しますが、次のように値を割り当てるフィールドは定義されません.
trait Foo {
  var bar: String
}

//           

trait Foo {
  def bar: String
  def bar_=(x: String)
}

抽象可変値は、サブクラスでvarまたはdefを使用して定義できますが、注意すべき点があります.
trait Foo {
  var bar: String
}

//    var     
class ConcreteFoo extends Foo {
    override var bar: String = "hello"
}

//    def     
class ConcreteFoo extends Foo {
    override def bar: String = "hello"
    override def bar_=(x: String) = x
}

上記のコード例では、コンパイルは可能であるが、bar_=メソッドは、xの値をベアラするために付与可能なフィールドがないため、実際の意味を持たない.
抽象型(Abstract Type)
抽象型の宣言は、サブクラスで具体化する必要があるプレースホルダです.つまり、具体的な定義は継承システムの最下位に落ちます.
通常、抽象タイプの使用は、上界の制約に合わせて行われます.以下に示します.
class Food
abstract class Animal {
  type SuitableFood <: food="" def="" eat="" suitablefood=""/>

パス依存型(Path-dependent types)
パス依存型の「パス」は、farm.barn.bessy、farm、barn、bessyなどのオブジェクトの変数名を指します.
名前から理解できるように、このタイプはパスに依存し、異なるパスは異なるタイプを与える.
Java内部クラスとの違い
1.パス依存型ネーミング(names)外部オブジェクト、内部クラスネーミング外部クラス名2.ScalaではOuter#Innerで表され、JavaではOuter.Innerで表される
その他
1.パス依存型のインスタンスは外部オブジェクトの参照を持つ.new Outer#Innerを直接使用してパス依存タイプを初期化することはできません.
改良タイプ(Refinement Type)
改良タイプとは、サブタイプ間に互換性のあるメンバー(compatible members)が含まれているため、構造化されたサブタイプ(structural subtyping)であり、簡単なサブタイプ関係を得ることができる.
構造サブタイプに対応する名前付きサブタイプ(Nominal subtyping)は、通常、より便利で、短い識別子を持つため、構造サブタイプに表示されるリスト・メンバー・タイプとは異なり、より簡単です.
しかし、構造サブタイプは通常より柔軟で、多くの面白いことを抽象化することができます.
// animal that eats grass
Animal { type SuitableFood = Grass }

class Pastrue {
  var animals: List[Animal { type SuitableFood = Grass}] = Nil
}

列挙
Scalaにおける経路依存型の応用例を列挙する
object Color extends Enumeration {
  val Red, Green, Blue = Value
}

Color.ValueはRed,Green,Blueのパス依存型である.