DEV Community

Daniel Inoa
Daniel Inoa

Posted on

Ternary Unwrapping in Swift


The Issue

Often enough I find myself writing something like this:

let foo: Foo
if let unwrappedDependency = optionalDependency {
    foo = Foo(unwrappedDependency)
} else {
    foo = Foo(fallback)
}
Enter fullscreen mode Exit fullscreen mode

Here I have a foo that needs to be instantiated but its dependency is an optional that has to be dealt with first. This is easily solved by unwrapping said optional through an if-let or guard-let statement. Simple, but it takes just too many lines (depending how you count) to express such a simple flow of statements, which could potentially be repeated many times throughout a codebase. This calls for a more elegant solution.

Swift already comes with some great constructs like the ternary and nil-coalescing operators that help in writing more expressive code and reducing verbosity for code as the one described above.

// Nil Coalescing Operator
let optionalNumber: Int? = ...
let number: Int = optionalNumber ?? nonOptionalfallback
Enter fullscreen mode Exit fullscreen mode
// Ternary Conditional Operator
let condition: Bool = ...
let number: Int = condition ? 1 : 0
Enter fullscreen mode Exit fullscreen mode

However, neither of these operators would be helpful in satisfying the requirements for an inline statement that:

  • evaluates an optional
  • provides a path for a successful unwrapping
  • provides a path if the unwrapping fails

The Solution

Since there's no operator that meets those requirements, we can then leverage Swift's ability to define and implement a new operator that would allow us to do just that by combining the ternary conditional and nil-coalescing operators.
I'm going to call it ternary unwrapping operator, and it looks like this:

let _ = optional ?? { unwrapped in ... } | ...
Enter fullscreen mode Exit fullscreen mode

and it allows us to simplify the aforementioned flow like this:

let foo: Foo = optionalDependency ?? { Foo($0) } | Foo(fallback)
Enter fullscreen mode Exit fullscreen mode

Let's dissect this:

  • Between = and ?? we have an optional that's to be evaluated.
  • If said optional can be safely unwrapped then the statement between ?? and | is executed. Here we have a closure that forwards the unwrapped optional and is used to create an instance of the expected return type (in this case Foo).
  • The statement after | serves as a fallback and must also resolve to an instance of the expected return type.

Use Case

Out of all the many scenarios where this operator could be useful, I've found that it is especially useful when doing string interpolation with an optional.

Instead of doing this:

// This approach is overwrought.
let optionalNumber: Int? = 1
let sentence: String?
if let number = optionalNumber {
    sentence = "My favorite number is \(number)"
} else {
    sentence = nil
}
Enter fullscreen mode Exit fullscreen mode

or this:

// This approach force unwraps which isn't ideal.
let optionalNumber: Int? = 1
let sentence: String? = optionalNumber != nil ? "My favorite number is \(optionalNumber!)" : nil 
Enter fullscreen mode Exit fullscreen mode

We could do this instead:

// This approach is concise and leverages the ternary unwrapping operator.
let optionalNumber: Int? = 1
let sentence = optionalNumber ?? { "My favorite number is \($0)" } | nil
Enter fullscreen mode Exit fullscreen mode

Under The Hood

This ternary unwrapping operator is in fact 2 infix operators used in conjunction.

precedencegroup Group { associativity: right }

infix operator ??: Group
func ?? <I, O>(_ input: I?,
               _ handler: (lhs: (I) -> O, rhs: () -> O)) -> O {
    guard let input = input else {
        return handler.rhs()
    }
    return handler.lhs(input)
}

infix operator |: Group
func | <I, O>(_ lhs: @escaping (I) -> O,
              _ rhs: @autoclosure @escaping () -> O) -> ((I) -> O, () -> O) {
    return (lhs, rhs)
}
Enter fullscreen mode Exit fullscreen mode
  • This first operator, func ??, takes 2 parameters: an optional and a tuple. This function ?? by itself is in fact sufficient as an operator. However, func | is needed to improve the style that match that of a ternary operator.
  • The second operator, func |, acts as a façade by taking 2 closures and returning them in a tuple; later used by ??.

I (input) and O (output) describe the type of the optional argument and that of the returning value (used in the assignment), respectively.


Closing Thoughts

One of my main priorities as a developer is to improve the readability of the code I write, especially if it is to be reviewed by my peers. I'd like to believe that what the ternary conditional operator has done for if-else statement, this new operator could for the if-let-else unwrapping statement; helping developers write code that will be easy for others to understand.

This ternary unwrapping operator, however, is best suited for cases such as when the instantiation or assignment of an object/value depends on 1 optional dependency needing to be unwrapped. For more complex cases it might be wiser to stick with if-let and guard-let. As always, all the operators described here are best used sparingly when readability of code can truly be improved, and not just to write fancy or unreasonably terse code.

Did you find this interesting and would you like to experiment with it? Check out the sample playground.

Thanks for reading!


See Also


This post was originally published on danielinoa.com

Discussion (4)

Collapse
aidantwoods profile image
Aidan Woods

Of course you can put arbitrary functions in either, so in general this operator will be useful – but when both code paths differ only in data you can reduce to a nil-coalescing though. E.g. the initial example can be written as ;-)

let foo = Foo(optionalDependency ?? fallback)
Collapse
aidantwoods profile image
Aidan Woods

Just in an attempt at playing code golf, the int example could also be written as

let sentence = optionalNumber.map { "My favorite number is \($0)" }

Though, of course again it's specific to the second code path happening to be convenient – in general the operator will remain more useful.

One could write

let sentence = optionalNumber.map { "My favorite number is \($0)" } ?? fallback()

But I think that fails in the direction of

improve[ing] the readability of the code

So your solution wins here ;-)

Collapse
danielinoa profile image
Daniel Inoa Author

Ultimately, it's a matter of taste.
The ?? | operator, however, allows me express the same idea in the style of an operator (ternary conditional) that's already common in many programming languages.

Collapse
tonyayoub profile image
Tony Ayoub

What about this:
let optionalNumber: Int? = 33
let sentence = optionalNumber.flatMap { "My favourite number is ($0)" }