SwiftでWhen-Then-Elseを表現してみる

こんにちは、iOSエンジニアの paper_and_paper です。

Kotlinには **switch文がない**って。
デビュー当初は思わず疑ってしまいましたが... もちろん周知の事実です。
では、何で制御するかというと **when式** という訳です。

今回は、Kotlinのwhen文をSwiftで実現してみよう〜というテーマでSwiftで頑張ってみました。
あくまでチャレンジなので、特別なメリット・デメリットは何もありません。敢えて言えば、Kotlinのwhen式についてiOS エンジニアでも理解できるくらいです。

どうぞよろしくお願いします🙏

## 1. when文の簡単なおさらい

### 1-1. when式の書き方

```kotlin
when(式){
    値1 -> 式の結果と値１が一致した場合の処理
    値2 -> 式の結果と値2が一致した場合の処理
    ...
    else -> 式の結果がどの値とも一致しない場合の処理
}
```

whenに渡した式の結果により、処理を分岐します。最後のelseには、上記の値のいづれでもない場合の処理を記述しますが、必須ではありません。（nullableを扱う場合は例外です）

**Kotlin の公式ドキュメント:**
https://kotlinlang.org/docs/reference/control-flow.html#control-flow-if-when-for-while

なお、switch文で必要だったbreakも不要です。

```kotlin
val num = Random.nextInt(5)
when(num){
    0 -> println("大吉")
    1 -> println("中吉")
    2 -> println("小吉")
    4 -> println("大凶")
    else->println("吉")
}
```

### 1-2. whenも式である
when は式なので、その分岐結果から値を返すことができます。返す値の記述方法はif式と同じ、該当するブロックの最後に値を書いておきます。

```Kotlin:Kotlin
val value = "hoge"
val result = when(value) {
    "hoge" -> "ほげ"
    "fuga" -> "ふが"
    else -> "piyo"
}
println(result) // => ほげ
```

（余談） Swift だとこんなイメージになるのかな...？

```swift:Swift
let value = "hoge"
let result = { v -> String in
   switch v {
   case "hoge":
      return "ほげ"
   case "fuga":
      return "ふが"
   default:
      return "piyo"
   }
}(value)
print(result)  // => ほげ
```

### 1-3. if式をwhenで代替できる
whenの引数を省略した場合、条件分岐はシンプルなBooleanを返す式となります。そのため、シングルアロー (`->`) の左辺の結果が **true** の場合には、右辺の処理が実行されます。つまり、**whenを if-else if の代替として使用することもできます。**

```kotlin
when {
   Booleanを返す式1 -> 処理1
   Booleanを返す式2 -> 処理2
   ...
   else -> 処理n
}
```

### 1-4. 型のチェックとの組み合わせ
Kotlin にも変数の型をチェックするため `is`演算子があります。これとwhenを組み合わせて、ある変数が目的の型かどうかをチェックすることが可能です。ある変数が目的の型かどうかをチェックすれば、チェック済みの型を持つ変数として使用できます。

```kotlin
val a: Any = "Kotlin"
when(a) {
   is Int -> print(a + a) // aはキャストなしでInt型として扱える
   is String -> println(a.toUpperCase()) // aはキャストなしでString型として扱える
}
```
### 1-5. switch文との主な相違点

- switch が whenになった
- case: が `->` になった
- 各条件の処理の終わりには break が不要となった
- default が else になっている

## 2. Swiftで実現したいこと💪

早速、始めていきましょう。

今回はSwiftでwhen式ライクな記述を表現するにあたり、
次のようなa-cを満たすような実装を検討していきます。

### a. `if-else`構文としてのWhen式
1-3のようにif式をwhenで代替できる。

```swift
let isProduction = true
when(isProduction)
    .then { print("production configuration is loaded") } // prints
    .else { print("debug configuration is loaded") }
```

### b. 返り値を扱った`if-else`のWhen式 
1-2のようにwhenを式として扱える。例えば、whenの返り値を結果として出力できる。

```swift
let isProduction = false
let state = when(isProduction)
    .then(1)
    .else(0)

print(state) // prints 0
```
### b. 複数ケースに関するマッチング制御としてのWhen式

```swift

let price = 1000
when(price)
    .greater(than: 2000) { print("Mercedes-benz") }
    .greaterThan(orEqual: 1000) { print("BMW") } // prints
    .equal(to: 800) { print("AUDI") }
    .equal(to: 600) { print("Peugeot") }
    .lower(than: 200) { print("Aqua") }
    .lowerThan(orEqual: 100) { print("No Car") }
    .else { print("Cyber Truck") }
// 出力結果
"BMW"

let newAge = 0
when(newAge)
    .greater(than: 50) { print("Bentley") }
    .greaterThan(orEqual: 40) { print("AUDI RS6") }
    .equal(to: 40) { print("AUDI A3") }
    .equal(to: 20) { print("Ford Fiesta") }
    .in(0...5) { print("Remote Controlled Cars") } // prints
    .not(in: 0...5) { print("Metal Cars") }
    .found(in: [0, 1]) { print("Electric Bike!") } // prints
    .in(0..<5) { print("Bike") } // prints
    .not(in: 0..<5) { print("Trolley") }
    .else { print("Cyber Truck!!!") }

// 出力結果
"Remote Controlled Cars"
"Electric Bike!"
"Bike"
```

## 3. Whenをどう定義するか？

whenは **式** なので、まずはグローバルな関数 when() として定義していきます。

```swift
func when<Value>(_ value: Value) -> Some<Value> {
    Some(input: value)
}
```

さらに返り値には `Some<Value>` という構造体を導入します。(Swift5.1から導入された Opaque Result Type `some` とは別物です。) この構造体は、then-elseでの構文制御の責務を任せる予定です。

```swift
struct Some<Input> {
    fileprivate let input: Input
    private let isTerminated: Bool

    init(input: Input, isTerminated: Bool = false) {
       self.input = input
       self.isTerminated = isTerminated
    }

    func `else`(_ execute: () -> Void) {
       guard !isTerminated else { return }
       execute()
    }

    func `else`<Value>(_ value: Value) -> Value {
        guard !isTerminated else { return value }
        return value
    }
}
```

`when()` で与えられたValueに応じてパターンマッチができるよう、上記ではInputという型パラメータを導入しています。型パラメータ value を使って then, else を定義した時に、引数からの型推論が成功すればコンシステントな仕組みが成立するはずです。

また、**else**を省略するケースを実現するため`isTerminated`フラグを導入することにします。（もっと良い方法があるはずだが...）通常は `false` なので `else` まで記述することになりますが、 `true` をセットしてあげれば else を省略させることができます。


### その１. Inputの制約条件: Bool型

`Some`の拡張条件として、Bool型のInputを検討してみます。

```swift
extension Some where Input == Bool {
    @discardableResult
    func then( _ execute: () -> Void) -> Self {
        guard input else { return self }
        execute()
        return Some(input: input, isTerminated: true)
    }

    @discardableResult
    func then<Value>( _ value: Value) -> Some<Value> {
        guard input else {
            return Some<Value>(input: value, isTerminated: false)
        }
        return Some<Value>(input: value, isTerminated: true)
     }

}
```

### その２. Inputの制約条件: Comparableプロトコルへの準拠

`Some`を拡張して、型パラメータ **Input** に **Comparable** プロトコル を制約条件として与えてあげれば、 range や closed range を使った比較なども実現することができます。

```swift
extension Some where Input: Comparable {

    @discardableResult
    func `in`(_ range: ClosedRange<Input>, _ execute: () -> Void) -> Some<Input> {
        guard range.contains(input) else { return self }
        execute()
        return Some(input: input, isTerminated: true)
    }

    @discardableResult
    func `in`(_ range: Range<Input>, _ execute: () -> Void) -> Some<Input> {
        guard range.contains(input) else { return self }
        execute()
        return Some(input: input, isTerminated: true)
    }

    @discardableResult
    func not(in range: ClosedRange<Input>, _ execute: () -> Void) -> Some<Input> {
        guard !range.contains(input) else { return self }
        execute()
        return Some(input: input, isTerminated: true)
    }

    @discardableResult
    func not(in range: Range<Input>, _ execute: () -> Void) -> Some<Input> {
        guard !range.contains(input) else { return self }
        execute()
        return Some(input: input, isTerminated: true)
    }

    @discardableResult
    func found(in values: [Input], _ execute: () -> Void) -> Some<Input> {
        guard values.contains(input) else { return self }
        execute()
        return Some(input: input, isTerminated: true)
    }

    @discardableResult
    func greater(than inputValue: Input, _ execute: () -> Void) -> Some<Input> {
        return compare(>, inputValue, execute)
    }

    @discardableResult
    func greaterThan(orEqual inputValue: Input, _ execute: () -> Void) -> Some<Input> {
        return compare(>=, inputValue, execute)
    }

    @discardableResult
    func lowerThan(orEqual inputValue: Input, _ execute: () -> Void) -> Some<Input> {
        return compare(<=, inputValue, execute)
    }

    @discardableResult
    func lower(than inputValue: Input, _ execute: () -> Void) -> Some<Input> {
        return compare(<, inputValue, execute)
    }

    @discardableResult
    func equal(to inputValue: Input, _ execute: () -> Void) -> Some<Input> {
        return compare(==, inputValue, execute)
    }

    /// Helper method for comparison
    @discardableResult
    private func compare(
        _ comparator: (Input, Input) -> Bool,
        _ inputValue: Input,
        _ execute: () -> Void
    ) -> Some<Input> {
        guard comparator(input, inputValue) else { return self }
        execute()
        return Some(input: input, isTerminated: true)
    }

}
```

完成👏


## References
[Control Flow: if, when, for, while - Kotlin](https://kotlinlang.org/docs/reference/control-flow.html)
[Swift Control Flow With When-Then-Else - medium.com](https://medium.com/swlh/swift-control-flow-with-when-then-else-79e5c933e40c)