iosgenericsswift5

How to call an optional array extension from another?


An extension ...

fileprivate extension Optional {
    ///Do an upsert for one item - works on all groups
    mutating func upsert<T: MyProtocol>(item: T) where Wrapped == [T] {
    }
}

which you can call like this

for i in updates.dogs {
   stuff.dogs.upsert(item: i)
}
for i in updates.cats {
   stuff.cats.upsert(item: i)
}
... dozens of those ...
... dozens of those ...
for i in updates.chooks {
   stuff.chooks.upsert(item: i)
}

dogs etc is a [Dogs]?, Dogs Cats etc are structs of MyProtocol

It would be nicer to

stuff.dogs.upsertAll(from: updates.dogs)
stuff.cats.upsertAll(from: updates.cats)

Soooo ...

example of protocol array error

If you

fileprivate extension Optional {
    mutating func upsertAll<T: MyProtocol >(from: [T]) where Wrapped == [T] {
        ..
        for i in from {
            self!.upsert(item: i)

Result,

Value of type 'Array<T>' has no member 'upsert'

self! is an array of [Dogs] ans Dogs etc are MyProtocol.

Is there a way?


Solution

  • I'm not sure where the idea of unwrapping self (self!) came from. upsert is an extension on Optional, so you should not unwrap self in upsertAll if you want to call upsert.

    fileprivate extension Optional {
        mutating func upsertAll<T: MyProtocol>(from: [T]) where Wrapped == [T] {
            for i in from {
                self.upsert(item: i)
            }
        }
    }
    

    Also consider taking in any type that is a Sequence<T> if all you need is the ability to use a for loop on it.

    mutating func upsertAll<S: Sequence<T>, T: MyProtocol>(from: S) where Wrapped == [T] {
        for i in from {
            self.upsert(item: i)
        }
    }
    

    Also consider relaxing the restriction on Wrapped to a more general protocol type (e.g. BidirectionalCollection), though of course this depends on what you are doing in upsert.

    I am assuming that upsert will still have some side effect even if self is nil. If that is not the case, it is rather un-idiomatic to declare upsert as an Optional extension. It would be more idiomatic to declare it as an extension of Array, and the call site would use optional chaining (stuff.dogs?.upsert(item: dog)) to call it.

    fileprivate extension Array where Element: MyProtocol {
        
        mutating func upsert(item: Element) {
        }
        
        mutating func upsertAll(from: some Sequence<Element>) {
            for i in from {
                self.upsert(item: i)
            }
        }
    }
    

    The key difference here (compared to your attempted extension on Array) is to use the Element type in the method declarations.