Converting dynamicType from a property to an operator
01 何が問題だったのか
Swiftでは、ある値の「実行時の型(dynamic type)」をメタタイプとして取り出すために、dynamicType というプロパティを使っていました。
let x: Any = 42
let t = x.dynamicType // Int.Type
ただし、この dynamicType は文法上プロパティとして定義されていたため、Swift の仕組み上いくつかの不自然さがありました。
あらゆる式のコード補完に dynamicType が出てしまう
dynamicType が通常のプロパティだったため、どんな値に対してもメンバ候補として提示されてしまいました。たとえば 4.dynamicType や myFunction().dynamicType のように、本来は意味のある操作ではない場面でもコード補完に dynamicType が混ざり、ノイズになります。
「型固有のプロパティ」ではなく「任意の式に適用する操作」である
通常のプロパティは「ある型に固有の属性」を表します。しかし dynamicType は特定の型に属する属性ではなく、「任意の式に対して、その値の動的な型を返す」という操作で、実態は sizeof のような演算子寄りです。プロパティという見た目が実態と噛み合っていませんでした。
02 どのように解決されるのか
dynamicType をプロパティではなく、式に適用する演算子のような形に作り直します。元の提案では dynamicType(value) という名前で導入する案でしたが、レビューの過程で標準ライブラリの関数 type(of:) として整理されることになり、Swift 3.0 ではこの形で実装されました。
基本的な使い方
値の動的な型は、type(of:) に値を渡して取り出します。プロパティアクセスではなくなるので、.dynamicType のような書き方は使えません。
let x: Any = 42
let t = type(of: x) // Int.Type
// Before (Swift 2):
// let t = x.dynamicType
返ってくるのは従来の dynamicType と同じく、実行時の型を表すメタタイプです。プロトコル型の値に対して呼べば、そこに格納されている具体型を取り出せます。
protocol Animal {}
struct Dog: Animal {}
struct Cat: Animal {}
let animals: [Animal] = [Dog(), Cat()]
for a in animals {
print(type(of: a)) // Dog, Cat
}
.dynamicType からの移行
既存コードは、x.dynamicType を type(of: x) に置き換えるだけで移行できます。プロパティアクセスから関数呼び出しの形に変わる点以外は意味は同じで、コンパイラのFix-itが機械的な書き換えを補助します。
// Before
let t1 = 42.dynamicType
let t2 = myFunction().dynamicType
// After
let t1 = type(of: 42)
let t2 = type(of: myFunction())
なぜプロパティをやめたのか
type(of:) を関数形式にしたことで、任意の式のメンバ補完に dynamicType が出てこなくなり、「値に固有のプロパティ」と「式に対する操作」の区別が見た目にも反映されます。関数として書くことで、呼び出しの形自体が「この値の型を取り出す」という操作であることを読み手に示せます。
なお提案当初は dynamicType(x) という名前のコンパイラ組み込み演算子として導入する案でしたが、コア関数と同じ関数呼び出しの見た目で統一するほうが自然だという判断から、レビューを経て type(of:) という名前で標準ライブラリ側に置く形に整理されました。将来的に言語側の機能が十分に整えば、標準ライブラリの普通の関数として実装し直せるよう意図されています。