こんにちは、iOSエンジニアの paper_and_paper です。
Kotlinには switch文がないって。
デビュー当初は思わず疑ってしまいましたが... もちろん周知の事実です。
では、何で制御するかというと when式 という訳です。
今回は、Kotlinのwhen文をSwiftで実現してみよう〜というテーマでSwiftで頑張ってみました。
あくまでチャレンジなので、特別なメリット・デメリットは何もありません。敢えて言えば、Kotlinのwhen式についてiOS エンジニアでも理解できるくらいです。
どうぞよろしくお願いします🙏
1. when文の簡単なおさらい
1-1. when式の書き方
when(式){
値1 -> 式の結果と値1が一致した場合の処理
値2 -> 式の結果と値2が一致した場合の処理
...
else -> 式の結果がどの値とも一致しない場合の処理
}
whenに渡した式の結果により、処理を分岐します。最後のelseには、上記の値のいづれでもない場合の処理を記述しますが、必須ではありません。(nullableを扱う場合は例外です)
Kotlin の公式ドキュメント:
https://kotlinlang.org/docs/reference/control-flow.html#control-flow-if-when-for-while
なお、switch文で必要だったbreakも不要です。
val num = Random.nextInt(5)
when(num){
0 -> println("大吉")
1 -> println("中吉")
2 -> println("小吉")
4 -> println("大凶")
else->println("吉")
}
1-2. whenも式である
when は式なので、その分岐結果から値を返すことができます。返す値の記述方法はif式と同じ、該当するブロックの最後に値を書いておきます。
val value = "hoge"
val result = when(value) {
"hoge" -> "ほげ"
"fuga" -> "ふが"
else -> "piyo"
}
println(result) // => ほげ
(余談) 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 の代替として使用することもできます。
when {
Booleanを返す式1 -> 処理1
Booleanを返す式2 -> 処理2
...
else -> 処理n
}
1-4. 型のチェックとの組み合わせ
Kotlin にも変数の型をチェックするため is
演算子があります。これとwhenを組み合わせて、ある変数が目的の型かどうかをチェックすることが可能です。ある変数が目的の型かどうかをチェックすれば、チェック済みの型を持つ変数として使用できます。
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で代替できる。
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の返り値を結果として出力できる。
let isProduction = false
let state = when(isProduction)
.then(1)
.else(0)
print(state) // prints 0
b. 複数ケースに関するマッチング制御としてのWhen式
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() として定義していきます。
func when<Value>(_ value: Value) -> Some<Value> {
Some(input: value)
}
さらに返り値には Some<Value>
という構造体を導入します。(Swift5.1から導入された Opaque Result Type some
とは別物です。) この構造体は、then-elseでの構文制御の責務を任せる予定です。
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 を省略させることができます。
その1. Inputの制約条件: Bool型
Some
の拡張条件として、Bool型のInputを検討してみます。
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)
}
}
その2. Inputの制約条件: Comparableプロトコルへの準拠
Some
を拡張して、型パラメータ Input に Comparable プロトコル を制約条件として与えてあげれば、 range や closed range を使った比較なども実現することができます。
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
Swift Control Flow With When-Then-Else - medium.com