前置き
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に合わせる必要がある。