3

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
Marcin Kapusta
  • 5,076
  • 3
  • 38
  • 55

2 Answers2

1

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?>`
Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • Thank You very much! This is it! You nailed it. I was thinking and thinking how to achieve this but could not figure it out. Thanks for this example with Contextual Where Clause. I was trying with OptionalType famous trick but your solution is much more Swifty. Thanks again! – Marcin Kapusta Mar 16 '23 at 22:43
0

After great help of Daniel T. I have found another idea how to write this extension with the generic where clause at extension level. I was not aware that I can define generic type T just after extension type on this scope level. So here is the solution based on Daniel answer.

extension PrimitiveSequence<T> where Trait == SingleTrait, Element == T?, T: WithIdResponse {
    func id() -> Single<String?> {
        self.map { $0?.id }
    }
}
Marcin Kapusta
  • 5,076
  • 3
  • 38
  • 55