Swift enum型を復習する

# 概要

初心者向けの記事です。初心者でなくてもなんらかの発見があるかもしれないです。
Swiftの文法のenumについて書きます。

動作はSwift5.3で確認

# 最初の問題
```
SwiftのenumでRawValueとAssociatedValueは同時に利用可能か？
```

これがわかった人は、特にこの記事を読む必要がないかもです。

# enum
列挙型と言う。
enumとcaseで定義します。

```swift
enum CompassPoint {
    case north
    case south
    case east
    case west
}
```



## 変数に格納

変数は**単数形**でかく。
変数がどのenum型かわかれば`.east`のように型を省略できる。

```swift
var directionToHead = CompassPoint.north
directionToHead = .south

var directionToHeadLast: CompassPoint = .east
```

# Switch
Switch文では、enumの全てのcaseを網羅しないとコンパイルエラー。
defaultを使うことも可能。

```swift
switch directionToHead {
case .north, .east, .south, .west: print("direction")
}

```

```swift
switch directionToHead {
case .north, .east, .south: print("direction")
default: print("other")
}
```

## イテレーションを回す

CaseIterableをつけることでenumの要素をfor文などで回せます。

こんな感じです

```swift

enum CompassPoint: CaseIterable {
    case north
    case south
    case east
    case west
}


CompassPoint.allCases.forEach{print($0)}
```

これは以下が出力されます。

```
north
south
east
west
```

# Associated Values

先ほどのenumの各要素になんらかの値を持たせることができるものです。

```swift

enum Appliance {
    case light(isOn: Bool)
    case airCon(degree: Int, isOn: Bool)
}

var appliance: Appliance
appliance = .light(isOn: true)
appliance = .airCon(degree: 28, isOn: true)
```

Switch文で使うときはletやvarでAssociated Valueを取り出せます。

```swift
switch appliance {
case .light(let isOn):
    print(isOn)
case .airCon(let degree, let isOn):
    print(isOn, degree)
}
```

先ほど出たCaseIterableは**Associated Valuesのあるenumには使えない**ので注意!


## ちょい技
associated valueが全部letだったり全部varの時は次のようにかけます。

```swift

switch appliance {
case let .light(isOn):
    print(isOn)
case let .airCon(degree, isOn):
    print(isOn, degree)
}
```

`let`を前に出して、letを複数個書かなくて良くなる！



# Raw Values

Associated Valuesと違って、**書いた時点で値の決まっている**値をenumの各caseに入れられます。

```swift
enum Step: Int {
    case one = 1
    case two = 2
    case three = 3
}

print(Step.three.rawValue)
```

これは`3`が表示されます。

```swift
enum Step: Int {
    case one, two, three
}

print(Step.one.rawValue)
```

これは何が表示されると思いますか？

実は

```
0
```
が表示されます。

IntもしくはStringのRaw Valueの時だけ、勝手に値が入ります。
Intの場合は、`0`が初めのcaseに入り１つずつインクリメントされます。
Stringの場合は、caseの名前そのものが入ります。

## さらに研究
RawValueについてさらにいろいろやってみましょう。
問題を出すので、出力される文字列を当ててみましょう。

### Q1

```swift
enum Step: Int,CaseIterable {
    case one, two, three=99
}

Step.allCases.forEach{print($0.rawValue)}
```

<details><summary>Q1答え</summary><div>

```
0
1
99
```
</div></details>

### Q2 

```swift

enum Step: Int,CaseIterable {
    case one, two=99, three
}

Step.allCases.forEach{print($0.rawValue)}
```

<details><summary>Q2答え</summary><div>

```
0
99
100
```

</div></details>

### Q3

```swift

enum Step: Int,CaseIterable {
    case one=12, two=11, three
}

Step.allCases.forEach{print($0.rawValue)}
```

<details><summary>Q3答え</summary><div>

コンパイルエラーです。
threeはtwoの11をインクリメントした12が入るのですが、これがoneとかぶるので怒られます。

</div></details>


### Q4

```swift

enum Step: Int8,CaseIterable {
    case one, two=127, three
}

Step.allCases.forEach{print($0.rawValue)}

```


<details><summary>Q4答え</summary><div>

これもコンパイルエラー
Int8のmaxは127なので、threeの値がInt8のオーバーフローすると怒られます。

```
error: MyPlayground.playground:12:24: error: integer literal '128' overflows when stored into 'Int8'
    case one, two=127, three
```

</div></details>



## RawValueで初期化
rawValueからenumにすることができます。
enumにない値を指定すると、**nil**になるので注意！

```swift

enum Step: Int, CaseIterable {
    case one=1, two, three
}

Step(rawValue: 2) // two
Step(rawValue: 4) // nil
```

つまりrawValueで初期化したときに返り値は、Optionalになります。
rawValueでの初期化を行うときには、Optional Bindingを使いましょう

```swift
guard let step = Step(rawValue: 4) else {
    fatalError("undefined!!")
}
```
# SwiftのenumでRawValueとAssociatedValueは同時に利用可能か？

最初の問題ですね。

答えは、`できる`

普通にやった場合は、こちらのコード

```swift

enum CompassPoint: String {
    case north(Int)
    case south(Int)
    case east(Int)
    case west(Int)
}
```
コンパイルエラーとなってしまいます。

```
'CompassPoint' declares raw type 'String', but does not conform to RawRepresentable and conformance could not be synthesized
```

しかし、実はなんとかなる。

今回説明したenumのRawValueの書き方は、`shorthand notation`といいます。以下のような書き方です。

```swift

enum Test: String {
    case pass = "やったー！"
}
```


RawValueを実現する条件は、上のコンパイルエラーにもありますが、`RawRepresentable`というプロトコルへの準拠です。
caseにAssociatedValueが１つでもあると、`shorthand notation`では`RawRepresentable`には自動的に準拠しなくなってしまいます。
なので、以下のようにすればRawValueとAssociatedValueは同時に利用できます。

```swift
enum TestResult {
    case pass(Int)
    case fail(Int)
}

extension TestResult: RawRepresentable {
    typealias RawValue = String
    
    init?(rawValue: RawValue) {
        switch rawValue {
        case "やったー！": self = .pass(1)
        case "残念、、": self = .fail(0)
        default:
            return nil
        }
    }
    
    var rawValue: RawValue {
        switch self {
        case .pass: return "やったー！"
        case .fail: return "残念、、"
            
        }
    }
}

TestResult.pass(1).rawValue // やったー！
TestResult.fail(0).rawValue // 残念、、

```

注意点は、initの時にAssociatedValueを入れなければいけないことです。

結局`shorthand notation`のRawValueの書き方というのは、ただの短縮表現であり、コンパイラとしては上のように`RawRepresentable`になるよう作っているようです。(参考: [The RawRepresentable Protocol in Swift – Ole Begemann](https://oleb.net/blog/2016/11/rawrepresentable/))



# 再帰のenum
再帰的にenumをAssociatedValueに使いたい場合には、indirectキーワードをそのcaseにつける必要があります。

```swift

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression) // ここの引数に自分自身が入っているから再帰
}
```

indirectはenumの最初に持ってくることも可能です。

```swift
indirect enum ArithmeticExpression: CustomStringConvertible {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
}

```

## indirectなしだと？
コンパイルエラー

```swift
error: MyPlayground.playground:2:6: error: recursive enum 'ArithmeticExpression' is not marked 'indirect'
enum ArithmeticExpression: CustomStringConvertible {
     ^
indirect 
```

## ちょっと文字列表示やってみた

```swift
let expression = ArithmeticExpression.addition(.number(10), .addition(.number(1), .number(2)))

print(expression.description)
```

上のprintで式を文字列で表示したいです。
CustomStringConvertibleを使って、再起的に式の文字列を表示してみました。


```swift
enum ArithmeticExpression: CustomStringConvertible {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    
    var description: String {
        get {
            switch self {
            case .number(let num):
                return String(num)
            case .addition(let expr1, let expr2):
                return "\(expr1.description) + \(expr2.description)"
            }
        }
    }
}
```
こんな感じでしょうか？



```
10 + 1 + 2
```
無事表示されました！


# まとめ
* swiftのenumで覚えるべきなのはAssociated ValuesとRawValue
* Associated Valuesはcaseに値を持たせられる
* RawValueはcaseに対応した値を設定できる。
* **Associated ValuesとRawValueは同時に利用できる**
* 再帰のenumは`indirect`を忘れずに!




# 参考
* [Enumerations — The Swift Programming Language (Swift 5.3)](https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html)
* [enums - Can associated values and raw values coexist in Swift enumeration? - Stack Overflow](https://stackoverflow.com/questions/24171814/can-associated-values-and-raw-values-coexist-in-swift-enumeration)
* [The RawRepresentable Protocol in Swift – Ole Begemann](https://oleb.net/blog/2016/11/rawrepresentable/)