🌯 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)
}
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
// Ternary Conditional Operator
let condition: Bool = ...
let number: Int = condition ? 1 : 0
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 ... } | ...
and it allows us to simplify the aforementioned flow like this:
let foo: Foo = optionalDependency ?? { Foo($0) } | Foo(fallback)
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 caseFoo
). - 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
}
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
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
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)
}
- 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!