0

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?

gohnjanotis
  • 6,513
  • 6
  • 37
  • 57

3 Answers3

3

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.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • Thanks. Is there a way I can evaluate in the closure of `map` or another function even when it is nil? – gohnjanotis Oct 13 '20 at 16:58
  • 1
    Why do you need to, though? You can just do `return entries.firstIndex {...}.map { entries[$0] }`, or just use `.first(where:)` – New Dev Oct 13 '20 at 17:00
  • Thanks. I saw that in the documentation, but it is a confusing method signature to me because I didn’t understand the “throws” and “rethrows” syntax and couldn’t find a good explanation of it. – gohnjanotis Oct 13 '20 at 22:15
  • I was mostly focusing on the fact that the closured gets a `Wrapped` - i.e. the non-optional value. The `throws` & `rethrows` part isn't really related to your question, but if you want, you could read more about the differences [here](https://stackoverflow.com/questions/43305051/what-are-the-differences-between-throws-and-rethrows-in-swift). – New Dev Oct 13 '20 at 22:22
1

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.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
1

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.