【Swift】共用型の列挙型

###1.はじめに
---
前回に`列挙型`について解説しましたが、今回は事前にお伝えした通り、`共用型の列挙型`について解説をしていこうと思います。

前回の記事と非常に深い関わりがあるので、まだご覧になっていない方はこちらの記事もご覧ください。
(https://qiita.com/0901_yasyun/items/a87c49438db68241778b)

###2.共用型の列挙型の概要について
---
`共用型の列挙型`は、`実体型`を指定しないシンプルな`列挙型`と、複数の異なる`タプル`の構造を併せ持つことができる型です。
この型にはいく通りかの異なるデータを、共通の概念でまとめて扱えるようにする狙いがあります。

まず、`共用型の列挙型`の定義の概要を次に示します。
シンプルな`列挙型`のケース名後ろに`タプル`の宣言を付けた形になっています。

```
enum 型名 {
    case ケース名 タプル型宣言
    case ケース名 タプル型宣言
    ... ...
```

次に具体的な定義例を示します。
この例では、Webページなどの作成などで使用する色の指定は、文字列の色名、16進数によるカラーコードなどで行われますが、どの形式でも色を指定するという目的は同じなので、これを１つのデータ型で表すことを考えます。

```Swift:
enum WebColor {
    case name(String)       // 色の名前
    case code(String)       // 16進カラーコード
    case white, black, red  // よく使う色
}
```

この定義を使って、様々なインスタンスを生成できます。

```Swift:
let background = WebColor.name("indigo")   // インディゴブルー
let turquoise: WebColor = .code("#40E0D0") // ターコイズ
let textColor = WebColor = .black          // 黒
```

**タプル型の構造に記述される情報**を以下では`付加情報`、または`ペイロード`と呼びます。
`共用型`の定義には、`実体型`を記述することはできません。
`値型`と`共用型の列挙型`は別のものとして記述しなくてはなりません。

前回のシンプルな`列挙型`、`値型の列挙型`では要素同士を比較することができましたが、`共用型の列挙型`の場合、自分で==演算子を定義するか、プロトコルEqutableを採用しない限り、**相互に比較はできません**。

```Swift:
if turquoise == WebColor.code("#40E0D0")  // エラー。比較できない
```

もう少し複雑な例を示します。
次の例では、市営地下鉄で販売されている数種類の切符やプリペイドカードのどれもが、自動改札機で処理できるとし、切符やカードの種類を`列挙型`で定義しました。

```Swift:
enum Ticket {
    case 切符(Int, Bool, 回数券:Bool)  // 普通券：価格、小人、回数券かどうか
    case カード(Int, Bool)             // プリペイドカード：残高、小人
    case 敬老バス                      // 敬老バス
}
```

caseに記述する括弧の中は`タプル`で、項目にキーワードを付けることもできます。
このような`列挙型`に対する処理を書き分けるために、`switch文`を使います。
caseで定数や変数へ値を代入でき、`付加情報`を使わない場合には、下の「.カード」のように**括弧以降を記述しないこともできます**。

以下では、**caseの後に置かれる構造に関する条件の記述**を`caseパターン`と呼びます。
また、where以下に条件を書くことができるのは`タプル`を扱う場合と同じです。
次の例を確認してください。

```Swift:
switch t {
case let .切符(fare, flag, _):
    print("普通券: \(fare) " + (flag ? "小人" : "大人"))
case .敬老バス:
    print("敬老バス")
case .カード(let r, true) where r < 110:  // 小人の最低運賃
    print("カード: 残高不足")
case .カード(let r, false) where r < 230: // 大人の最低運賃
    print("カード: 残高不足")
case .カード:
    print("カード")
```

`タプル`をswitch文で使う場合と同様、`付加情報`を定数に代入するにはletを()の中に置く方法と外側に置く方法が使えます。

```Swift:
case let .切符(fare, flag, _):     // または
case .切符(let fare, let flag, _):
```

キーワード付きのタプルの場合、caseパターン内にはラベルを書かなくて大丈夫ですが、書くこともできます。
letとともに使う場合、letを書く場所に注意が必要です。

```Swift:
case let .切符(fare, _, 回数券: flag):     // または
case .切符(let fare, _, 回数券: let flag):
```

###3.if-case文について
---
与えられたタプルが、**複数の条件のどれに一致するか**を調べるには、switch文が便利に利用できました。
しかし、**特定の１つの条件のみに一致するかどうか**調べたいという場合もあります。

例えば、先ほどの例で用いたTicket型の変数pが、プリペイドカードかどうかだけ調べたいとします。しかし、if文で次のようには記述できません。

```Swift:
if p == .カード {              // エラー。この記述はできない
    print("プリペイドカード")
}
```

switch文なら次のように記述できますが、使わないとわかっているdefault節も書かなければならず、少々面倒です。

```Swift:
switch p {
case .カード: print("プリペイドカード")
default: break
}
```

そこで、こういった場合でも容易に記述できる構文が用意されています。
上の例は次のように記述できます。この構文を`if-case文`と呼ぶことがあります。

```Swift:
if case .カード = p {
    print("プリペイドカード")
```

条件はまず`caseパターン`を記述し、「=」と対象となる式を置きます。
`caseパターン`と式が一致するかどうかは、switch文の場合と同様に処理されます。
switch文の場合と異なり、付加的な条件はwhere節ではなく、カンマ「,」で区切って後ろに続けます。

この記法はif文、while文、および`guard文`の条件の書き方で説明したものと同じで、カンマで区切って、一般の式、`オプショナル束縛構文`などを記述可能です。

```
if caseパターン = 式, 条件 {.....}
```

次の例では、変数tの内容が残額が1200円以上のプリペイドカードかどうかを調べます。

```Swift:
if case .カード(let y, _) = t, y >= 1200 { .....
```

少し複雑な例として、辞書のインスタンスからTicket型の要素を取り出し、その情報がパターンに一致しているかを調べるという条件を記述してみましょう。

```Swift:
let tickets:[String:Ticket] = [ "志倉" : .切符(260, false, 回数券: true), 
               "佐々木" : .切符(220, false, 回数券:false)]
let name = "佐々木"
if let t = tickets[name], case .切符(220, _, _) = t {
    print("220円券")
}
```

この例の場合、辞書から得た情報が`オプショナル型`なので、まず`オプショナル束縛構文`で定数tにTicket型のインスタンスを得ます。
nilの場合はそこでifの条件が不成立となりますが、nilでなければ、次に定数tが220円の切符かどうか調べます。

なお、上のif文は「?」を使うと次のように記述することもできます。

```Swift:
if case .切符(220, _, _)? = tickets[name] {
    print("220円券")
}
```

###4.for-in文でcaseパターンを使う
---
for-in文でも、`caseパターン`を利用できます。
これまでに説明したfor-in文と同様に、配列などの`Sequenceプロトコル`に適合した式をinの次に置き、インスタンスを次々に取り出します。

この書き方では、取り出されたインスタンスとcaseの次のパターンが一致した場合にだけ、コードブロックを実行します。
実行のための条件をさらに記述する必要があれば、where節を追加することもできます。

```
for case パターン in 式 where 式 {
    文... ...
}
```

たとえば、Ticket型の要素を持つ配列passesから次々にインスタンスを取り出して、220円より高い切符の情報を表示するには次のようにします。

```Swift:
for case let .切符(fare. child, coupon) in passes where fare > 220 {
    var k = coupon ? "回数券" : "普通券"
    if child { k += "(小人)" }
    print(k, "\(fare)円")
}
```

この文は次のように記述するのと同じです。

```Swift:
for t in passes {
    switch t {
    case let .切符(fare, child, coupon):
        if fare > 220 {
            var k = coupon ? "回数券" : "普通券"
            if child { k += "(小人)" }
            print(k, "\(fare)円")
        }
    default: break
    }
}
```

この構文で`オプショナル型`に当てはまる「?」を利用すると、`オプショナル型`が含まれるデータ列をうまく扱うことができます。
また、letではなく、varを使うこともできます。

###5.再帰的な列挙型
---
`共用型の列挙型`で、タプルの内部に自分自身を要素として指定したい場合がありえます。
例えば次のような場合を考えてみます。

```Swift:
enum メッセージ {
    case 文書(String, String)     // 差出人、文書
    case データ(String, [Int8])   // 差出人、データ列
    case 転送(String, メッセージ)  // 差出人、メッセージ
}
```

このデータの宣言は間違っていないようにも見えますが、Swiftでは扱うことができません。

Swiftでは`列挙型`は`値型`のデータですので、直感的に説明するならば、負荷情報は`列挙型`のデータを表すメモリ位置に直接並べて格納されています。
上記のように自分自身を含む、つまり**再帰的なデータ構造はデータを格納するために必要なメモリ容量がコンパイル時に決定することができません**。

これに対して`参照型`のデータは、メモリ上のどこかに存在する実体を間接的に参照するためのための情報なので、メモリの問題はありません。

しかし、`再帰的な列挙型`が使えると便利な場面も多いので、Swiftでは`列挙型`が**自分自身を付加情報に含む場合、該当するcaseの前にindirectというキーワードを記述します**。
これを`間接指定`と呼びます。`indirect`、つまり間接であるとは、その部分だけポインタのように間接参照を行うことを意味します。

今の話を以下の例に示します。
`CustomStringConvertibleプロトコル`を採用し、printで表示できるようにしています。

```Swift:
enum メッセージ : CustomStringConvertible {
    case 文書(String, String)              // 差出人、文書
    case データ(String, [Int8])            // 差出人、データ列
    indirect case 転送(String, メッセージ)  // 差出人、メッセージ
    
    ver description: String {
        switch self {
        case let .文書(from, str): return from + "(" + str + ")"
        case let .データ(from, _): return from + "[データ]"
        case let .転送(from, msg): return from + "←\(msg)"
        }
    }
}
```

実行例を示します。

```Swift:
let m1 = メッセージ.文書("伊藤", "休みます")
let m2 = メッセージ.転送("白石", m1)
let m3 = メッセージ.転送("山田", m2)
print(m3)   // "山田←白石←伊藤(休みます)"を出力
```

再帰的な`caseパターン`がいくつかある場合には、enumの前に`indirect`を置いて全体に間接指定を行うことができます。

###6.おわりに
---
今回は共用型の列挙型についての記事を書きましたが、プロトコルやオプショナル型の扱いについては、記事が少し長くなってしまうので今回は解説していません。
来週以降はクラスなどについての解説をしていこうと思います。
ここまで読んでくれた方、ありがとうございました。