Requiring Leading Dot Prefixes for Enum Instance Member Implementations
01 何が問題だったのか
Swift の enum のケースは、言語の枠組みとしては型のインスタンスメンバーではなく 静的メンバー の位置付けです。実際、構造体やクラスの static プロパティをインスタンスメソッドから使うときには、常に完全修飾(TypeName.member)が必要です。ところが enum のケースだけは、インスタンスメソッドやイニシャライザの中から型名もリーディングドットも付けずに参照できる という特別扱いがされていました。
enum Coin {
case heads, tails
func printMe() {
switch self {
case heads: print("Heads") // リーディングドットなし
case .tails: print("Tails") // リーディングドットあり
}
if self == heads { // リーディングドットなし
print("This is a head")
}
if self == .tails { // リーディングドットあり
print("This is a tail")
}
}
init() {
let cointoss = arc4random_uniform(2) == 0
self = cointoss ? .heads : tails // リーディングドット有無が混在
}
}
言語全体の慣習との不整合
Swift では、型が文脈から一意に決まる場面でケースを書くときに、.heads のようなリーディングドット付きの略記を使うのが慣習になっています。これは switch 文や関数呼び出しの引数など、enum 実装の外側では一貫して守られているルールです。
一方で enum の中から自分自身のケースを参照する場合に限っては、リーディングドットも型名も省略できてしまうため、「ケースは静的メンバーなのに、インスタンスメンバーと同じ感覚で裸の名前で書ける」という他に類のない例外ができていました。
最小驚きの原則に反する
この例外は、読む側にも書く側にも混乱をもたらします。同じ enum 実装の中で、あるケースは .heads、別のケースは heads と書かれていても両方コンパイルが通るため、スタイルのぶれが発生しやすくなります。他の静的メンバーに対するルールとも一貫しておらず、Swift の「Principle of Least Astonishment(最小驚きの原則)」に反する挙動として整理の対象になりました。
02 どのように解決されるのか
enum のインスタンスメソッドやイニシャライザの中からケースを参照するときは、リーディングドット(.caseName)または完全修飾(EnumType.caseName)を必ず付ける ことをコンパイラが要求するようにします。これにより、enum 実装の内側と外側でケースの書き方が統一されます。
enum Coin {
case heads, tails
func printMe() {
switch self {
case .heads: print("Heads") // OK
case .tails: print("Tails") // OK
}
if self == .heads { // OK
print("This is a head")
}
}
init() {
let cointoss = arc4random_uniform(2) == 0
self = cointoss ? .heads : .tails // どちらもリーディングドット必須
}
}
リーディングドットを付けずに裸の heads / tails と書いた場合はコンパイルエラーになります。
static メンバーの扱いは変えない
この変更はケース(static な列挙子)のみを対象にしています。enum に定義されたその他の static メソッド・プロパティの扱いはこれまで通りです。
- インスタンスメソッドから static メンバーを呼ぶには、これまで通り完全修飾が必要です。
- static メソッドから 同じ型の static メンバー(static メソッドやケース)を呼ぶときは、従来通りリーディングドットや型名なしで書けます(これは構造体やクラスの static メンバー同士と同じ挙動です)。
enum Coin {
case heads, tails
static func doSomething() { print("Something") }
static func staticFunc() { doSomething() } // static 同士は裸でOK
static func staticFunc2() { let foo = tails } // static からケースも裸でOK
func instanceFunc() { type(of: self).doSomething() } // インスタンスからは完全修飾が必要
func otherFunc() { if self == .heads { /* ... */ } } // インスタンスからケースはリーディングドット必須
}
結果として、「インスタンスメソッドから static メンバーを呼ぶには型を明示する」という Swift 全体のルールに、enum のケースだけが持っていた例外がなくなり、統一されたメンタルモデルで書けるようになりました。