Search code examples
swiftrx-swiftswift-extensions

Referencing instance method 'xxx' on 'PrimitiveSequence' requires the types 'A' and 'any B' be equivalent


We need some advice. I'm trying to do such abstraction so I have many different Response's. At some time in the project we realized that some of our Responses have id property and we want to make some common logic for those Responses without taking care about what this response is. Only what matters is that those Responses contains id field. We we introduced WithIdResponse and we created extensions for Responses that contains id and are useful in context of implemented architecture.

Next we created Reactive dummy extension for Single that do simple mapping WithIdResponse? -> String? and we called this operator id.

Now the problem with Swift we have that when we are using this id operator we have such errors from compiler: Referencing instance method 'id()' on 'PrimitiveSequence' requires the types 'ItemAResponse' and 'any WithIdResponse' be equivalent

We are trying to understand this message but we failed. Are we doing something wrong here with our assumptions?

import Foundation
import RxSwift

protocol WithIdResponse {
    var id: String { get }
}

extension PrimitiveSequence where Element == WithIdResponse?, Trait == SingleTrait {
    func id() -> Single<String?> {
        self.map { $0?.id }
    }
}

struct ItemAResponse {
    let id: String
}
extension ItemAResponse: WithIdResponse {}

struct ItemBResponse {
    let id: String
}
extension ItemBResponse: WithIdResponse {}

let subjectA: BehaviorSubject<ItemAResponse?> = BehaviorSubject(value: nil)
let subjectB: BehaviorSubject<ItemBResponse?> = BehaviorSubject(value: nil)

let singleA = subjectA.asSingle().id() // HERE WE HAVE ERROR

Solution

  • You are requiring the Element to be an Optional<any WithIdResponse>. You need your generic type to allow an implementation of any WithIdResponse.

    Here is how you can do it:

    extension PrimitiveSequence where Trait == SingleTrait {
        func id<Wrapped>() -> Single<String?> where Element == Optional<Wrapped>, Wrapped: WithIdResponse {
            self.map { $0?.id }
        }
    }
    

    BTW, did you know there is already an Identifiable type in the Swift library? I suggest you use that instead of making your own.

    Something like this:

    extension PrimitiveSequence where Trait == SingleTrait {
        func id<Wrapped>() -> Single<Wrapped.ID?> where Element == Optional<Wrapped>, Wrapped: Identifiable {
            self.map { $0?.id }
        }
    }
    
    struct ItemAResponse: Identifiable {
        let id: String
    }
    
    let subjectA: BehaviorSubject<ItemAResponse?> = BehaviorSubject(value: nil)
    
    let singleA = subjectA.asSingle().id() // This is a `PrimitiveSequence<SingleTrait, String?>`