Reputation: 6428
I'd like to add an optional "filter" parameter to a function that processes a list of strings. If the filter is nil
, I'd like to process all the strings, otherwise I'd only like to process the ones that contain the filter. Roughly:
func process(items: [String], filter: String?) {
for item in items {
if filter == nil || item.contains(filter) {
// Do something with item
}
}
}
The typechecker complains about passing filter into contains
since it's optional, but contains
takes a String
. I could of course force-unwrap, but that seems ugly. The above would compile in Kotlin because it would smart-cast away the optional, but what's the idiomatic way to express this in Swift?
Thanks!
Upvotes: 1
Views: 933
Reputation: 534893
The key here is (and always was going to be) the nil-coalescing operator. This lets you unwrap an Optional safely if it is not nil
, but if it is nil
, you substitute another value.
The issue with the question, however, is that it poses itself in an unSwifty way. You would never write a filtering function that takes an Optional String. You would write a filtering function that takes an Optional filtering function! Instead of limiting the caller to contains
and a String, you want to allow any type of array and any filtering function. That is the Swifty way to write this function.
And while we are at it, the function should not predetermine what is done with the members of the array either. That should be another function that the caller passes in!
Thus we end up with this far more general and much Swiftier statement of the goal:
func process<T>(_ arr: [T],
filtering filterPred: ((T)->Bool)? = nil,
processing processPred: ((T)->Void)) {
// ...
}
Okay! So let's write this function. The processing
part is easy; this is the predicate to a forEach
call. Like this:
func process<T>(_ arr: [T],
filtering filterPred: ((T)->Bool)? = nil,
processing processPred: ((T)->Void)) {
arr.forEach(processPred)
}
Very good! But we have neglected to deal with the filtering
function. Let's take care of that. Clearly, this is where the rubber meets the road. And just as clearly, it is evident that this is a function to be passed into filter
. But we can only do that by unwrapping if it is not nil
. What if it is nil
? The OP has specified that in that case we should let all element through the filter.
So consider a predicate function that is to be passed into filter
. What is the function that lets everything through? It is:
{ _ in true }
So that is what to put after the nil-coalescing operator! Therefore, this is the full answer:
func process<T>(_ arr: [T],
filtering filterPred: ((T)->Bool)? = nil,
processing processPred: ((T)->Void)) {
arr.filter(filterPred ?? { _ in true }).forEach(processPred)
}
And here are two tests:
let arr = [1,2,3,4]
self.process(arr) { print($0) }
self.process(arr) { $0 % 2 == 0 } processing: { print($0) }
Upvotes: 1
Reputation: 154513
There is a map
on Optional that executes the provided closure only if the optional is not nil
. You can use map
along with the nil coalescing operator ??
to provide the default value of true
if the map
returns nil
because the filter
is nil
:
func process(items: [String], filter: String?) {
for item in items {
if filter.map(item.contains) ?? true {
// process item
}
}
}
Upvotes: 6