Swift Digest
SE-0068 | Swift Evolution

Expanding Swift Self to class members and value types

Proposal
SE-0068
Authors
Erica Sadun
Review Manager
Chris Lattner
Status
Implemented (Swift 5.1)

01 何が問題だったのか

Swift 2 時点では、インスタンスから「自分自身の型」を取り出して使う方法に、型の種類や文脈によっていくつかの不便が残っていました。

クラスの中でしか Self を書けなかった

Swift にはもともと Self というキーワードがあり、プロトコル宣言や、クラスのメソッドシグネチャ上で「自分の型」を表す用途に使えました。しかし値型(struct / enum)の内部や、クラスのメソッド本体で同じように書くことはできません。スタティックメンバを呼ぶには型名を明示するか、self.dynamicType を使う必要がありました。

struct MyStruct {
    static func staticMethod() { ... }
    func instanceMethod() {
        MyStruct.staticMethod()
        self.dynamicType.staticMethod()
    }
}

型名の直書きは壊れやすい

スタティックメンバを呼ぶたびに MyStruct.staticMethod() のように型名を書くと、次のような問題がありました。

  • 型名が長い(MyExtremelyLargeTypeName.staticMember のようなケース)と可読性が落ちます。
  • 型名を変えるたびに、参照箇所を全部書き直す必要があります。
  • 「その型自身に属するスタティックメンバを呼びたい」という意図が、具体型名の繰り返しで埋もれてしまいます。

self.dynamicType は冗長で意図が伝わりにくい

もう一つの選択肢である self.dynamicType.staticMethod() は、型を直書きしないぶん移植性はありますが、記述として冗長で、Swift が目指す簡潔さと意図の明快さの両方に反していました。さらに dynamicType はすべて小文字化されていないキーワードで、Swift の命名規約の中でも例外的な存在として浮いていました。

クラスでは型名と動的な型が一致しないことがある

クラスに非 final なスタティック/クラスメンバがあると、TypeName.classMemberself.dynamicType.classMember は同じ意味になりません。前者は宣言時の型に固定されるのに対し、後者は実行時の型(サブクラスであればサブクラス側)で解決されます。値型であれば両者は一致しますが、クラスでは「どちらを書くかで挙動が変わる」点を常に意識しなければならず、self.dynamicType を置き換える簡潔な書き方が欲しい状況でした。

02 どのように解決されるのか

Self を、値型の内部とクラスメンバの本体でも使えるように拡張し、「自分自身の型」を指す統一的な書き方として使えるようにします。Swift 5.1 で実装されています。

元の提案には self.dynamicTypex.Self という書き方に置き換える案も含まれていましたが、コアチームのレビューで後半は切り離され、前半(Self の使用範囲拡大)のみが採択 されました。dynamicType の扱いは別提案として検討することになり、後に type(of:) へと形を変えて整理されることになります。

値型の中で Self が書ける

structenum のメソッドやプロパティの中で、Self を「自分の型」として使えます。値型には継承がないため、Self は宣言されている型そのものを表します。

struct MyStruct {
    static func staticMethod() -> Int { 42 }

    func instanceMethod() -> Int {
        // これまでは MyStruct.staticMethod() や
        // self.dynamicType.staticMethod() と書く必要があった
        Self.staticMethod()
    }
}

型名を書き換えても本文の Self はそのまま使えるため、リネームや汎用的なコードの書きやすさが向上します。

クラスメンバの本体で Self が書ける

クラスのメソッドやプロパティの本体でも、Self を書けます。この場合の Self は、そのインスタンスの実行時の型(動的な型) を意味します。final なクラスでは宣言された型と一致しますが、非 final なクラスでは、サブクラスから継承されたインスタンスでは Self がそのサブクラス側を指します。

class Animal {
    class func makeDefault() -> Self {
        // ここでの Self は動的な型。
        // Dog のインスタンスから呼ばれれば Dog、
        // Animal のインスタンスから呼ばれれば Animal を指す。
        return self.init()
    }

    required init() {}
}

class Dog: Animal {}

let a = Animal.makeDefault() // Animal
let d = Dog.makeDefault()    // Dog

これは既存の Self(プロトコル要件やクラスメソッドの戻り値型として使われていたもの)の挙動と自然に一致します。クラス内で Self.classMember と書いたときと、TypeName.classMember と書いたときで意味が変わり得る点は従来どおりで、前者はサブクラス側でオーバーライドされたメンバを呼ぶのに対し、後者は宣言時の型に固定されます。

採択されなかった部分

提案の後半にあった「x.dynamicTypex.Self に改名する」案は、今回のスコープから外されました。dynamicType の置き換えは、その後 SE-0096 で type(of:) という関数形式に整理されています。本提案で導入されたのはあくまで「Self を書ける場所の拡大」であり、self.dynamicType(当時)と Self は別物として扱われます。

利点

これらの変更により、次のような書き分けができるようになります。

  • 「その型に属するスタティックメンバを、型名を直書きせずに呼びたい」場合は Self を使います。
  • 「具体的なその型のメンバを、継承関係にかかわらず固定して呼びたい」場合は、従来どおり TypeName.member を明示します。

Self と型名の直書きが、それぞれ別の意図を持った表現として使い分けられる形に整います。