Search code examples
swiftgenerics

Extension optional Array with Optional Element. Is it even possible?


I have a protocol FooProtocol. and a class Bar<Foo:FooProtocol>. Inside a class an Array var mess: [Foo?]? to keep [foo1, foo2, nil, foo3...] or nil And I try to make extension for this array to count new Foo object. I prefer to have protocols, because Foos could be very different objects delivered from outer world.

protocol FooProtocol {
  ....
  init(from heaven: Int)
}

extension Optional where
  Wrapped: Collection,
  Wrapped.Element == Optional,
  Wrapped.Element.Wrapped: FooProtocol // 'Wrapped' is not a member type of 'Wrapped.Element'
{
  var united: Wrapped.Element.Wrapped { // Nope
    let i = ...
    return Wrapped.Element.Wrapped(from: i) // Nope
    
  }
}

class Bar<Foo:FooProtocol> {
  var mess: [Foo?]?
  init (with mess: [Foo?]?) {
    self.mess = mess
  }
  var important: Foo {
    return mess.united
  }
}

Any ideas? I'm blocked.

Edit 1:

After Leo suggestions I changed some parts of my code. But still stucked. This time more code from Playgrounds.

Any object that could be converted into '[Double]'. Could be color (as RGBA), Bezier curve, square, whatever...

public protocol FooProtocol {
    var atomized: () -> [Double] {get}
    static var count: Int {get}
    init(_ array:[Double])
    init()
}


public extension Array where Element: FooProtocol {
    var average: Element {
        var resultAtoms: [Double] = []
        let inputAtoms = self.map {$0.atomized()}
        for i in 0..<Element.count {
            let s = inputAtoms.reduce(into: 0.0, {$0 += $1[i]}) / Double (Element.count)
            resultAtoms.append(s)
        }
        return Element(resultAtoms)
    }
}

extension Optional where
    Wrapped: Collection,
    Wrapped.Element == Optional<FooProtocol>
{
    typealias Foo = Wrapped.Element.Wrapped // Doesn't work. How to get class?
    
    var average: Foo { // I cannot use Wrapped.Element, it's Optional
        if let thatsList = self {
            let withOptionals = Array(thatsList) // OK, its [Optional<FooProtocol>]
            let withoutOptionals = thatsList.compactMap({$0}) // OK, its [FooProtocol]
            // This is funny, called from class works and makes 'bingo'.
            return withoutOptionals.average // Error: Value of protocol type 'FooProtocol' cannot conform to 'FooProtocol'; only struct/enum/class types can conform to protocols
        } else {
            return Foo() // Hello? init Wrapped? Foo? How to get Foo()?
        }
    }
}

class Bar<Foo:FooProtocol> {
    var mess: [Foo?]?
    init (with mess: [Foo?]?) {
        self.mess = mess
    }
    func workOn() {
        let z:Foo = mess.average  // OK, I can make 'mess.average ?? Foo()' but prefer not do it
    }
    // Thats OK
    func workHard() { // To prove 'Array extension where Element: FooProtocol' works
        if let messExist = mess {
            let withoutOptionals =  messExist.compactMap({$0})
            let bingo = withoutOptionals.average //It's OK
        }
    }
}

class SomeFoo : FooProtocol {
    static var count = 3
    required init() {
        a = 0
        b = 0
        c = 0
    }
    
    required init(_ array: [Double]) {
        self.a = Int(array[0])
        self.b = Float(array[1])
        self.c = array[2]
    }
    
    var atomized: () -> [Double]  {
        return {return [Double(self.a), Double(self.b), self.c]}
    }
    
    var a: Int
    var b: Float
    var c: Double
}

let aFoo = SomeFoo([1, 2, 3])
let bFoo = SomeFoo([7, 9, 1])
let cFoo = SomeFoo([2, 6, 5])

let barData = [nil, aFoo, nil, bFoo, cFoo]
let barWithData = Bar(with: barData)
let barWithoutData = Bar<SomeFoo>(with: nil)

Maybe I should forget about extending array and make some functions inside a class (I'm almost sure I will need those functions somewhere else)

Edit 2

Even if I try to simplify and to make extension for Array I found troubles.

extension Array where
    Element == Optional<FooProtocol>
{
    func averageNils <Foo: FooProtocol>() -> Foo {
        
        let withOptionals = Array(self) // OK, its [Optional<FooProtocol>]
        let withoutOptionals = self.compactMap({$0}) // OK, its [FooProtocol]
        return withoutOptionals.average as! Foo // Error: Value of protocol type 'FooProtocol' cannot conform to 'FooProtocol'; only struct/enum/class types can conform to protocols
    }
}

Solution

  • From my understanding, it should work as you did, but one never knows what happens in the swift compiler world (and especially it's error messages).

    Anyway, you can circumvent digging deeper into Wrapped.Element.Wrapped by specifyig the Wrapped.Element more precisely to be an Optional<FooProtocol>:

    protocol FooProtocol {}
    class Foo : FooProtocol {}
    
    extension Optional where
      Wrapped: Collection, //OK
      Wrapped.Element == Optional<FooProtocol> // still good
    {
       var unfied: Wrapped.Element  // Should be 'Foo' if self is '[Foo?]?' {
       {
        return 1 == 0 ? nil : Foo()
       }
    }