Swift

Type Omission - 呼び出し時に型名を省略できるstatic fieldの話

More than 3 years have passed since last update.


前置き

try! Swiftお疲れ様でした!

try! Swift 資料まとめ - Qiitaniwatakoさんの書き起こし のおかげで、在宅組にもかかわらず沢山の知見を得ることができました。

さて、この記事では

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 を取ります。

ここでコンパイラは、 AaToA: 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 を継承したサブクラス CC1CC2 を作ります。

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 では、コンパイラは、求められている型 CC1c_inheritable: CC1 が定義されているか探します。

c_inheritable: C は、継承にあたって、より詳細に c_inheritable: CC1 と書くことができます。

期待通りfieldが見つかり、無事、コードは解釈されました。

一方、doWithCC2 では、求められている型 CC2c_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に合わせる必要がある。