Expressionize Swift's Switch

I've been having a little too much fun using Swift lately. It has this beautiful combination of object oriented and functional features that I find irresistible. Unfortunately nothing is perfect, so there's one thing Swift doesn't have that I really miss, control flow expressions.

In languages like F#, Rust, and Kotlin, if-else and switch are expressions, in Swift, if-else and switch are statements. For example, if we wanted to set the value of a variable based on the result of a switch statement, we would have to declare the variable outside of the switch, and then initialize the variable in the matching case.

let val = 3

let myString: String
switch val {
    case 1: myString = "the value is one"
    case 2: myString = "the value is two"
    case 3: myString = "the value is three"
    case 7: myString = "the value is seven"
    default: myString = "The value is not one, two , three, or seven"
}

print(myString) //"the value is three"

There's nothing wrong with doing it this way, I just prefer the explicitness of expressions. For example, we could set myString in F# like this:

let val = 3

let myString =
    match val with
    | 1 -> "the value is one"
    | 2 -> "the value is two"
    | 3 -> "the value is three"
    | 7 -> "thre value is seven"
    | _ -> "The value is not one, two, three, or seven"
    
printfn "%A" x //"the value is three"

Unlike statements, expressions produce values, so we're able to declare and set myString all at once. Because of the simplicity of the example, it's hard to see the value the expression based version brings, but it can make a difference when scanning code to determine the intent of the developer. I've found that expressions make it (subjectively) easier to determine the intent because they create a more natural flow in the code. In the Swift example, we have to declare an uninitialized variable first. If someone is viewing this code for the first time, they have no idea what myString is for at this point. Then we have to write our switch. The reader doesn't know what the switch statement is for until he or she reads through each of the cases and finds that the variable is set in the switch statement. In the F# example, the reader knows exactly what's happening from the start. A variable is declared and then will be initialized in the block that follows.

This got me thinking. In order to mimic the expression code, I need a way to immediately return a value from a block. How might I do that? A function! But what if I don't want to create a new function for a one-off call? Then I remembered our friend from JavaScript, the immediately invoked function expression, or IIFE.

How might we use the concept of an IIFE in Swift to mimic our F# expression? Well an IIFE in JavaScript is just an anonymous function. In Swift, we can use a closure expression to create an anonymous function, so let's try a closure.

Closures expressions in swift take the following form:

{ (parameters) -> return type in
    statements
}

Lets apply that to our switch:

let val = 7

let myString = { () -> String in
    switch val {
        case 1: return "the value is one"
        case 2: return "the value is two"
        case 3: return "the value is three"
        case 7: return "the value is seven"
        default: return "The value is not one, two, three, or seven"
    }
}()

print(myString) //"the value is a seven"

Works like a charm! Here we set the value of myString to the result of a closure expression, and then call it immediately.

If we want, we can make this immediately invoked closure expression (or IICE for short) self contained by passing the switch value as an argument to the IICE.

let val = 7

let myString = { myVal -> String in
    switch myVal {
        case 1: return "the value is one"
        case 2: return "the value is two"
        case 3: return "the value is three"
        case 7: return "the value is seven"
        default: return "The value is not one, two, three, or seven"
    }
}(val)

print(myString) //"the value is a seven"

And there we go, now we have a way to expressionize our switches! Now we can clearly express our intent for myString just as we did in our F# example.

One thing to keep an eye out for is that closure's in Swift are reference types and can create a strong reference cycle if you're not careful. If you find yourself using "self" in a closure, make sure you handle it with care. Read this great article from the Stable Kernel blog for more information on avoiding memory leaks when using closures.

I'll be the first to admit that I'm not well versed enough in Swift to know whether this is good or bad practice. But it is a fun exercise that could prove useful in certain situations.

Happy Coding!

Reference: Swift Closures, IIFE's, Prevent Memory Leaks in Closures: Stable Kernel