前置き
try! Swiftお疲れ様でした!
try! Swift 資料まとめ - Qiita や niwatakoさんの書き起こし のおかげで、在宅組にもかかわらず沢山の知見を得ることができました。
さて、この記事では
try! Swift Swiftヒップスター #tryswiftconf Day3-4 - niwatakoのはてなブログ
にある1枚のスライド
struct PowerLevel {
let value: Int
static func determinePowerLevel(_ fighter: ZFighter) -> PowerLevel
}
func powerLevelIsOver9000(powerLevel: PowerLebel) -> Bool {
return powerLevel.value > 9000
}
//Type omission!
if powerLevelIsOver9000(.determinePowerLevel(goku)) {
print("It's over 9000!")
}
enumの中の静的変数はenumを返してくる、同じことをstatic func でやっている。
static var が enum のように動くなら、タイプを省略できます
について詳しく調べてみようと思います。
疑問
「あーね、わかるよ、Enumで .Hoge
とか書けるのと同じだよね。 init(frame: CGRect.zero)
を init(frame: .zero)
って書けるやつだよね」
とあっさり納得しそうになったんですが、よく考えると不思議ですよね。
frameがCGRectなのは自明だから型推測が走る…のは分かる。
どこかの型に .zero: CGRect
が定義されていることまでは分かるだろうけれど、定義されているのがCGRectだということはどうやって判断するんだ?
Selfを返す場合には省略できるというルールなのか…?
- Type Omissionは、Selfを返す場合のみ有効?
- Type Omissionは、継承関係のあるclassでも有効なのだろうか?
- Type Omissionは、サブクラスではどう動くのか?
これらの各疑問を、コードとともに検証します。
疑問1. Type Omissionは、Selfを返す場合のみ有効?
検証コード
struct A {
static var aToA: A { return A() }
static var aToB: B { return B() }
}
struct B {
static var bToA: A { return A() }
static var bToB: B { return B() }
}
func doSomething(a: A) {}
doSomething(A.aToA)
doSomething(B.bToA)
doSomething(.aToA)
doSomething(.bToA) // error: Type 'A' has no member 'bToA'
なるほど、4つ目でビルドエラーになりました。
doSomething
は引数として型 A
を取ります。
ここでコンパイラは、 A
に aToA: A
, bToA: A
が定義されていることを期待するわけですね。
実際に bToA: A
が定義されているのは B
ですが、コンパイラが辿るのはあくまで A
だけなのだとわかりました。
疑問2. Type Omissionは、継承関係のあるclassでも有効なのだろうか?
検証コード
C
を継承したサブクラス CC
を作ります。
class C {
static var c: C { return C() }
class var c_inheritable: C { return C() }
}
class CC: C {
override class var c_inheritable: C { return CC() }
}
func doWithC(c: C) {
print(c)
}
doWithC(.c) // C
doWithC(.c_inheritable) // C
いけましたね! 親まっしぐら。
型として要求されているのは C
なので、サブクラスがどうなってようとガン無視です!
疑問3. Type Omissionは、サブクラスではどう動くのか?
検証コード
C
を継承したサブクラス CC1
と CC2
を作ります。
class CC1: C {
override class var c_inheritable: CC1 { return CC1() }
}
class CC2: C {
override class var c_inheritable: C { return CC2() }
}
func doWithCC1(cc: CC1) {
print(cc)
}
func doWithCC2(cc: CC2) {
print(cc)
}
doWithCC1(.c_inheritable) // CC1
doWithCC2(.c_inheritable) // error: type of expression is ambiguous without more context
doWithCC1
では、コンパイラは、求められている型 CC1
に c_inheritable: CC1
が定義されているか探します。
c_inheritable: C
は、継承にあたって、より詳細に c_inheritable: CC1
と書くことができます。
期待通りfieldが見つかり、無事、コードは解釈されました。
一方、doWithCC2
では、求められている型 CC2
に c_inheritable: CC2
が定義されているか探します。
しかしビルドエラーが発生。なぜなら、定義されているのは c_inheritable: C
だからです。
doWithCC2(.c_inheritable as! CC2) // error: type of expression is ambiguous without more context
こう書けば通るかも?と思ったんですが、ダメでした。キャストの問題ではなく、fieldの探索の問題ということですね。
まとめ
- 推測された型にSelfを返すstatic fieldがある場合、呼び出し時には型名を省略することができる。
- サブクラスでType Omissionを働かせるためには、戻り値の型もSelfに合わせる必要がある。