Swift Digest
SE-0036 | Swift Evolution

Requiring Leading Dot Prefixes for Enum Instance Member Implementations

Proposal
SE-0036
Authors
Erica Sadun, Chris Lattner
Review Manager
Doug Gregor
Status
Implemented (Swift 3.0)

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 のケースだけが持っていた例外がなくなり、統一されたメンタルモデルで書けるようになりました。