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 件のコメント:
コメントを投稿