[Swift] is 攻略ガイド(とりわけメタタイプとの絡みについて)

Swiftの`is`、特にメタタイプが絡んだときの挙動について整理しておきたかったんです。

## TL;DR

`インスタンス is 型名.Type` で、**「左辺のインスタンスが右辺のメタタイプとみなせるかどうか」**を判定する式になる。

以上のことさえ知れれば、ひとまずは十分だと考えます。
以降、理論的背景の解説など一切なく、個別の現象を書き並べただけの記事のためご注意ください。
パターンマッチ: `Type-Casting Patterns`における`is`に関する説明も本記事では割愛いたします。

## 環境

- Xcode 9.2
- Swift 4.0.3

## `is`の基本とその構文

- インスタンス(左辺)が、特定の型(右辺)、またはそのサブクラスとみなせるかを判定し、
`Bool`の値を返す。
- プログラムの静的な解析から、常に`true`、あるいは`false`を返すことが明らかな場合には、コンパイル時にエラーになることがある。[^1]
- 右辺に `型名.Type` と書くと、
**「左辺に置いたインスタンスが、右辺の型のメタタイプとみなせるか」**を判定する式になる。

[^1]: 詳解Swift 第3版 P175 より引用。たとえば `Int.self is Int?` というコードは当該環境でコンパイルエラーになることを確認しました。

```:構文
   cat                      is Animal
// [インスタンス or メタタイプ]    [型名 or 型名.Type(メタタイプの判定に用いる記法)]
```

上の解釈に照らし合わせると、`is`の記法は4つのパターンに区分できます。

|左辺 \ 右辺|型名|型名.Type|
|:-----------:|:------------:|:------------:|
|インスタンス|パターン1|パターン2|
|メタタイプ|パターン3|パターン4|

以下、上記テーブルの各象限について見ていきます。

### インスタンス × 型名 (パターン1)

「左辺のインスタンスが、右辺の型・もしくはそのサブタイプとみなせるかを判定する。」

例えば、以下の実行結果は自然です。

```swift
class Animal {}

let animal = Animal()
if animal is Animal {
    print("animalは、Animal型のインスタンスです。")
}

// 結果
// animalは、Animal型のインスタンスです。
```

### インスタンス × 型名.Type (パターン2)

「左辺のインスタンスが、右辺の型のメタタイプとみなせるかを判定する。」

例えば、以下の`animal`は当然メタタイプではないので、何も出力されません。

```swift
class Animal {}

let animal = Animal()
if animal is Animal.Type {
    print("animalは、Animal型のメタタイプです。")
}

// 結果
// 何も出力されない
```

### メタタイプ × 型名 (パターン3)

「左辺のメタタイプが、右辺の型・もしくはそのサブタイプとみなせるかを判定する。」

例えば、以下を実行すると、何も出力されません。
メタタイプは、その型のインスタンスとはみなされないようです。

```swift
class Animal {}

let metaAnimal: Animal.Type = Animal.self  // メタタイプを取得
if metaAnimal is Animal {
    print("metaAnimalは、Animal型のインスタンスです。")
}

// 結果
// 何も出力されない
```

### メタタイプ × 型名.Type (パターン4)

「左辺のメタタイプが、右辺の型のメタタイプとみなせるかを判定する。」
例えば、以下の実行結果も自然だと思います。

```swift
class Animal {}

let metaAnimal: Animal.Type = Animal.self  // メタタイプを取得
if metaAnimal is Animal.Type {
    print("metaAnimalは、Animal型のメタタイプです。")
}

// 結果
// metaAnimalは、Animal型のメタタイプです。
```

## 特殊なケース・注意点

前章までの内容で、大方のケースにおいて自信を持って`is`を使えるようになりました。
以下、`is`を使用していく中で遭遇した、直感に反するような挙動や注意点について列挙します。

### Protocolの場合

`is`の右辺には、プロトコル名も置くことができます。
その場合、左辺が右辺のプロトコルに適合したインスタンスの場合に`true`が返る、という挙動になります。

```swift
protocol Hoge         {}
class HogeClass: Hoge {}

let hogeClass = HogeClass()
print(hogeClass is Hoge)

// 結果
true
```

さらに、プロトコルにもメタタイプは存在し、
クラスや構造体・列挙体と同様、以下の記法で取得できます。
(その定義に`Self` もしくは `連想型(associatedtype)`を含まないプロトコルのみ取得が可能です。)

```swift
protocol Hoge {}
let metaHoge: Hoge.Protocol = Hoge.self  // プロトコル `Hoge` のメタタイプを取得
```

しかし、このように取得したメタタイプに対し、前述したテーブルのパターン4にあたる演算を行うと、
直感に反する結果が返ります。

```swift
print(metaHoge is Hoge.Type)

// 結果
false
```

この挙動は非常に不可解な印象を覚えます。

また注意点として、`Self` もしくは `連想型(associatedtype)`を含むプロトコルは、`is`の右辺になることができません。

```swift
protocol Base {
    associatedtype Element
}

print(42 is Base)

// 結果: コンパイルエラー
Protocol 'Base' can only be used as a generic constraint because it has Self or associated type requirements

print(42 is Equatable)

// 結果: コンパイルエラー ∵ `Equatable`は `==`の定義に`Self`を含むので、`is`の右辺で使えない。地味に不便やもしれない
Protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
```

### サブクラスが絡む場合

「あるクラスA`(Animal)`のサブクラス`(Dog)`のメタタイプ」は、
「あるクラスA`(Animal)`のメタタイプ」とみなされます。
これは、通常のスーパークラス-サブクラスの関係を鑑みれば、自然に感じられます。

```swift
class Animal      {}
class Dog: Animal {}

let metaDog = Dog.self
print(metaDog is Animal.Type)

// 結果
true  // サブクラスのメタタイプは、スーパークラスのメタタイプとみなされる
```

同様の理由で、こちらの挙動も、素直なものに感じます。

```swift
let metaAnimal = Animal.self
print(metaAnimal is Dog.Type)

// 結果
false  // スーパークラスのメタタイプは、サブクラスのメタタイプとはみなされない
```

<!--
### ジェネリクス型が絡む場合

以下のような、型パラメータ<T>を持つジェネリクス型があったとします。

```swift
class Container<T> {
    let element: T
    init(element: T) {
        self.element = element
    }
}
```

このクラスに対し、上記テーブルの4パターンの演算を行った場合の結果は以下となります。

```swift
let container: Container<Int> = Container(element: 42)

print(container is Container<Int>)       // true
print(container is Container<Int>.Type)  // false
print(Int.self  is Container<Int>)       // false
print(Int.self  is Container<Int>.Type)  // false

TODO: これ、

Container.self         is ...
Container<Int>.self    is ...
Container<String>.self is ...

などが漏れているので、あとで研究。

```

では、これが標準ライブラリの`Optional`型だった場合はどうでしょう?

```swift
public enum Optional<Wrapped> {
    case none
    case some(Wrapped)
}
```
-->

### ジェネリクス型を`is`の右辺に使う場合の注意点

以下のような、型パラメータ`<T>`を持つジェネリクス型があったとします。

```swift
class Container<T> {
    let element: T
    init(element: T) {
        self.element = element
    }
}
```

この型がクラスの場合、型パラメータ`<T>`を特殊化することなく、`is`の右辺に使うことができます。

```swift
let container = Container(element: 42)
print(container is Container)

// 結果
true
```

しかし、この型がクラスではなく構造体(または列挙体)だった場合は、
上記で可能だった記法がなぜかコンパイルエラーになってしまいます。

```swift
// クラスから構造体に変更
struct Container<T> {
    let element: T
    // 全項目イニシャライザがあるためこの`init`は本来不要だが、上記のクラス定義と対称性を保つためにあえて明示しています
    init(element: T) {
        self.element = element
    }
}

let container = Container(element: 42)
print(container is Container)

// 結果: コンパイルエラー
Cast from 'Container<Int>' to unrelated type 'Container<_>' always fails
```

このように、型パラメータ`<T>`を明示してやれば、`Container`が構造体であったとしても`is`の右辺で使えるようになります。

```swift
let container = Container(element: 42)

print(container is Container<Int>)     // true
print(container is Container<String>)  // false
print(container is Container<Any>)     // false (これは直感に反するけどな)
```

標準ライブラリにある`Array<Element>`が、型パラメータ`<Element>`を特殊化することなく`is`の右辺に使えないのも、
`Array`が構造体だからでしょう。
(とはいえ、構造体だとなぜダメなのか、の理由についてまでは理解が進んでいないのですが)

```swift
let array = [1,2,3]
print(array is Array)

// 結果: コンパイルエラー
Cast from '[Int]' to unrelated type 'Array<_>' always fails
```

この場合も、このようにすれば使えます。

```swift
let array = [1,2,3]

print(array is Array<Int>)     // true
print(array is Array<String>)  // false
print(array is Array<Any>)     // true(でもってこっちはtrueかよ...`Array`だけの特別措置なのかな)
```

###  その他

このような書き方も許容されるようです。

```swift
// リテラル`[]`だけでは、本来Arrayの型パラメータ`<Element>`は決定しないはずだが...
print([] is Int)        // false
print([] is [Int])      // true
print([] is Any)        // true
print([] is [Any])      // true
```

## おわりに

型に関する理解が深まり次第、随時追記いたします。
(特にジェネリクス型やオプショナル型が絡んだときの挙動は、今後の研究対象とさせてください)

## links

[Swift の変数で型を扱う](https://ez-net.jp/article/40/LspZyDVH/Q4bXi1rgZJmR/)
(`型名.Type`の意味がわからず悩んでいたところ、一気に疑問が氷解しました)