Don't use nested ternary operators in Swift

The ternary operator is a very handy notation to simply abbreviate if else constructs. And often it’s seen nested - one, two or three times. The compiler has no problems with such code. But we, as human beings, reach the limits.

Martin Fowler, a British-American software developer, author and international speaker on the subject of software architecture, said about code readability:

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.

Martin Fowler

Lets assume we have the following pre conditions:

let c1 = false
let r1 = "C1 is true"

let c2 = true
let r2 = "C2 is true"

let c3 = false
let r3 = "C3 is true"
let o3 = "C1 & C2 & C3 are not true"

Now a variable is to be assigned a value, depending on the 3 conditions c1, c2 and c3. The simplest approach could be:

var result = ""

if c1 {
    result = r1
} else if c2 {
    result = r2
} else if c3 {
    result = r3
} else {
    result = o3
}

print(result)
-> C2 is true

This code is absolutely correct and both the compiler and every developer can understand it with a blink of an eye.

But using such code can be a bit tricky if you want to apply it in a SwiftUI view modifier, for example to set a color or font of a Text view. In such a case, one tends to use a so-called ternary operator, the short form of if else in one line.

let result = c1 ? r1 : c2 ? r2 : c3 ? r3 : o3

// or less ugly (but still ugly)

let result = c1 ? r1 : (c2 ? r2 : (c3 ? r3 : o3))

print(result)
-> C2 is true

This code is also absolutely correct and returns the same result as the if else construct above. But honestly, we don’t need to discuss readability here.

We can of course use if else to directly assign the value to our result variable. It would look like this:

let result = {if c1 { r1 } else if c2 { r2 } else if c3 { r3 } else { o3 }}()

Or a bit better formatted:

let result = { if c1 {
        r1
    } else if c2 {
        r2
    } else if c3 {
        r3
    } else {
        o3
    }
}()

As of Swift 5.9 you can get rid of the needed closure, which results in code looking like this:

var result =
    if c1 {
        r1
    } else if c2 {
        r2
    } else if c3 {
        r3
    } else {
        o3
    }

But in my opinion it still isn’t good to read. What I want is nearly “plain english”. Just as if I were reading a sentence.

So I came to a very simple solution which is nothing else as syntactic sugar. But in my opinion very tasty sugar when it comes to read that code. Instead of using (nested) ternary operators we define one simple function:

@inlinable func when<T>(_ condition: Bool, then result: @autoclosure () -> T, else otherResult: @autoclosure () -> T) -> T {
    condition ? result() : otherResult()
}

Now you can set the result variable in a readable way:

let result = when(
    c1,
    then: r1,
    else: when(
        c2,
        then: r2,
        else: when(
            c3,
            then: r3,
            else: o3
        )
    )
)

Thanks to @lksz for pointing me to the missing @autoclosure.

Tagged


© Woodbytes