Finally, I've had some free time to cook up a draft for my previous post. I've focussed on filter
alone, because I'm afraid that lumping multiple proposals into one might cause them to all get rejected because of the disapproval of a subset.
Let me know what you guys think!
Add Optional.filter(_:)
to the Standard Library
- Proposal: SE-NNNN
- Authors: Alexander Momchilov
- Review Manager: TBD
- Status: rough draft
During the review process, add the following fields as needed:
- Implementation: apple/swift#NNNNN
- Decision Notes: Rationale, Additional Commentary
- Bugs: SR-NNNN, SR-MMMM
- Previous Revision: 1
- Previous Proposal: SE-XXXX
Introduction
The nil coalescing operator provides the ability to inject a value into a nil
optional. However, there is no current API for removing a value from an optional. This proposal introduces Optional.filter(_:)
to do exactly that.
Filter takes a closure that detemines whether or not you wish to keep a value. That way, the remaining part of an optional chain can treat certain kinds of values identically to nil
.
Swift-evolution thread: Discussion thread topic for that proposal
Motivation
Suppose you have some data that can be optional. Typically, part of your validation of this data would involve handling the nil
case with one of the existing mechanisms (such as conditional binding, ??
, !``Optional.map(_:)
, etc.). However, often times, there are other conditions undesirable conditions you wish to validate against, which you wish to handle in the same way.
For example, suppose getSomeInput()
returns a String
the user keyed in and you want to display it, and giving a nice message if the String
is nil
.
let userInput: String? = getSomeInput()
print(userInput ?? "No input provided")
But suppose you want to treat whitespace-only input as "No input provided"
. The current solution would be either quite laborious:
let input: String? = getSomeInput()
let output: String
if let input = input, !input.isOnlyWhiteSpace {
output = input
}
else {
output = "No input provided"
}
print(output)
... or quite non-intuitive to your average programmer:
let input: String? = getSomeInput()
print(input.flatMap { $0.isOnlyWhiteSpace ? $0 : nil } ?? "No input provided")
Proposed solution
Using the new Optional.filter(_:)
API shown below, the example could be solved with:
let input: String? = getSomeInput()
print(input.filter { !$0.isOnlyWhiteSpace } ?? "No input provided")
Detailed design
The implementation is short, simple, and discoverable:
extension Optional {
/// TODO: write documentation
@inlinable
public func filter(_ shouldKeep: (Wrapped) throws -> Bool) rethrows -> Optional {
guard let some = self else { return nil }
return try shouldKeep(some) ? some : nil
}
}
Source compatibility
This is purely additive change.
Effect on ABI stability
None.
Effect on API resilience
This would add a new public method to Optional
.
Alternatives considered
Do nothing. But this change is so lightweight and useful, I don't think it's worth passing up.
There are two approaches, which lead to different sets of viable names:
-
The closure returning
true
indicates "keep the value". In this case, appropriate names includefilter
orkeep(if:)
. E.g.input.filter(isValid)
,input.keep { !$0.isWhiteSpaceOnly }
. This is consistent withSequence.filter(_:)
-
The closure returning
true
indicates "replace the value withnil
". In this case, appropriate names includereject(if:)
,discard(if:)
. E.g.input.reject(if: isInvalid)
,input.discard { $0.isWhitespaceOnly }
This raises 2 questions:
- Which is more frequently used? An
isValid
predicate, or anisInvalid
predicate? - In the case that
isInvalid
is a more common predicate, does the popularity of the use-case justify breaking the consistency ofSequence.filter
?