Consider the following example:
var number: Int? = 42
// TODO: print number, if it contains a value.
Currently, Swift provides different ways to deal with Optional values.
The first technique that might come to mind is Optional Binding:
if let number = number {
print(number)
}
However, one could say that using a control structure is quite a verbose pick for such a simple task. This can be seen in the re-introduction of a now unwrapped number
variable, merely for calling a single function on it on the next line.
Another technique, Optional Chaining, is irrelevant in this case, as we're not accessing properties/methods on the number
variable.
The Nil-Coalescing Operator is yet another technique, perhaps in the form of:
print(number ?? _)
However, this is not currently possible, as there is no way to "bail out" without providing a default value.
A final option that might come to mind is taking advantage of Swift's higher-order functions:
number.map { print($0) }
The map(_:)
function definitely has the shape required for the task:
func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
In this example, The transform
closure has a signature of (Int) -> Void
, and the entire function is evaluated concretely to be ((Int) -> Void) -> Void?
However, going back to our original intention it's clear that nor are we mapping number
to a different value, neither we're interested in the outer return value - an Optional Void - which on itself seems unusual.
A transformation of this shape, i.e one that returns nothing, is not uncommon in our day-to-day tasks, and in my opinion deserves a name which clearly and conspicuously translates the writer's intent in call-site.
In this respect, another Container type in Swift that has been added a dedicated method to handle such "transformations" is the Array
type. Consider the following functions:
func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]
func forEach(_ body: (Element) throws -> Void) rethrows
While we would all agree that forEach(_:)
is not a necessity, we can also agree that for the task of printing all elements in an array, this:
[4, 2].forEach { print($0) }
reads much nicer than this:
_ = [4, 2].map { print($0) }
Thus I propose adding a parallel function to Array
's forEach(_:)
, taking advantage of a similar naming for convenience:
public extension Optional {
func forValue(_ body: (Wrapped) throws -> Void) rethrows {
guard let value = self else { return }
try body(value)
}
}
IMPORTANT NOTE:
A similar pitch was raised a couple of years ago, suggesting to add an ifPresent(_:)
function with a similar signature:
func ifPresent(@noescape f: (Wrapped) throws -> Void) rethrows
Unfortunately, the discussion quickly turned into a debate of whether to make Optional
into a CollectionType
.
Towards the end the misleading nature of map(_:)
was actually raised, but discussion didn't make any progress, and more specifically - didn't make the case for handling a particular kind of closure shape passed to a higher order function.
PS:
I'd like to thank my fellow iOS team members @Vimeo, Amir Aharon and Liron Yehoshua, for their wise input on this issue.