Reputation: 7575
I have a simple function to find a specific element by index in an array using firstIndex
like this, where Entry.id
is a String
:
private func findUserEntry(inEntries entries: [Entry],
currentUserID: String?) -> Entry? {
guard let currentUserID = currentUserID else { return nil }
guard let index = entries.firstIndex(where: { $0.id == currentUserID }) else { return nil }
return entries[index]
}
I was refactoring my code to try to accomplish the same thing in different ways, as a way to learn and practice. I thought this function would effectively do the same thing:
private func findUserEntry(inEntries entries: [Entry],
currentUserID: String?) -> Entry? {
guard let currentUserID = currentUserID else { return nil }
return entries
.firstIndex { $0.id == currentUserID }
.map { possibleIndex -> Entry? in
guard let index = possibleIndex else { return nil }
return entries[index]
}
}
However, I'm running into an error: Initializer for conditional binding must have Optional type, not 'Array<Entry>.Index' (aka 'Int')
. So, somehow, the value of possibleIndex
coming out of .firstIndex
into .map
is an Int
instead of the Int?
I expected, like it is in the original version above. Why is there a difference here?
Upvotes: 0
Views: 1322
Reputation: 131436
If you look up the Array function firstIndex(where:) in Apple's docs you'll find this: https://developer.apple.com/documentation/swift/array/2994722-firstindex
The function is declared to return an Optional Integer:
func firstIndex(where predicate: (Element) throws -> Bool) rethrows -> Int?
So no, it will ALWAYS return an Optional.
However, you are using map, which in this case is a function on Optional:
https://developer.apple.com/documentation/swift/optional/1539476-map
The function Optional.map() is declared like this:
func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
If the Optional
contains a value, it is unwrapped and passed to the closure of your map
statement. If the Optional
contains nil, the map function returns nil.
Thus in your code
private func findUserEntry(inEntries entries: [Entry],
currentUserID: String?) -> Entry? {
guard let currentUserID = currentUserID else { return nil }
return entries
.firstIndex { $0.id == currentUserID }
.map { possibleIndex in
//At this point the value in possibleIndex can't be nil,
//so there is no need for the guard statement.
//guard let index = possibleIndex else { return nil }
return entries[possibleIndex]
}
}
There is no need for your guard statement. If firstIndex contains nil, the map statement returns nil. If firstIndex contains a value, the map statement passes the unwrapped value in firstIndex to the closure and returns the result of that closure.
Upvotes: 1
Reputation:
Other people answered your question, but what you really want is flatMap
.
currentUserID.flatMap { currentUserID in
entries.first { $0.id == currentUserID }
}
Or, if Entry.id
is not optional, then remove the flat-mapping.
Upvotes: 1
Reputation: 49590
Array<Int>.firstIndex
returns an optional Array<Int>.Index
, which is an Int?
.
So, .map(_:)
that you're using is a method of Optional<Int>
with the following signature:
func map<U>(_ transform: (Wrapped) throws -> U) rethrows -> U?
Evaluates the given closure when this Optional instance is not nil, passing the unwrapped value as a parameter.
Upvotes: 3