Swift Control Flow With When-Then-Else

Keran Marinov
The Startup
Published in
3 min readOct 3, 2020

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 whensimilar to Kotlin.

Here are a few examples where the usage of when are demonstrated.

  1. When as if-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

--

--