Swift Control Flow With When-Then-Else
Swift has a variety of control flow mechanisms such as if
, guard
, and switch
statements. In this experimental blog, I will try to create something similar to Kotlin when
control flow. https://kotlinlang.org/docs/reference/control-flow.html
We can find out how much effort it would be needed to create the following APIs, converting if-else
to expression and using when
similar to Kotlin.
Here are a few examples where the usage of when
are demonstrated.
When
asif-else
statement
private func loadConfiguration() { let isProduction = true when(isProduction) .then { print(“production configuration is loaded”) } .else { print(“debug configuration is loaded”) }}
2. When
as if-else
an expression that returns a value
@discardableResultprivate func findState() -> Int { let isProduction = false let state = when(isProduction) .then(1) .else(0) print("state=", state) // prints 0 return state}
3. When
as a statement with multiple matching cases.
In this example, multiple matching cases are executed but else will be only executed if there is not any matching case.
private func displayMatchingCars() {let age = 40when(age) .greater(than: 50) { print("Bentley") } .greaterThan(orEqual: 40) { print("AUDI RS6") } // prints .equal(to: 40) { print("AUDI A3") } /// prints .equal(to: 20) { print("Ford Fiesta") } .lower(than: 10) { print("Toy Car") } .lowerThan(orEqual: 10) { print("No Car") } .else { print("Cyber Truck") }let newAge = 0when(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") } .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!!!") }}
Let’s start with the implementation of this API. We need a global function called when
that returns a type Some with a parameterized generic type called Value. Generic Value is inferred from the type of the argument.
func when<Value>(_ value: Value) -> Some<Value> { Some(input: value)}
Next, the implementation of Some struct is quite simple. The only detail here to be aware is the isTerminated flag which is always false but only set to true by the then function, which prevents else case to be ignored.
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 }
}
Extension of Some with Input as a boolean. The code is quite self-explanatory, in the case of then, it returns a new Some struct instance with isTerminated argument with true.
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)
}}
Extension of Some with Input that conforms to Comparable interface. You can how different methods are implemented in order to achieve comparison with range, closed range, etc..
I have created a sample project where you can find the full implementation with other examples.
Example Control Flow App -> https://github.com/k-marinov/control-flow-app