Specialise protocol with generic type

69 Views Asked by At

I encounter a problem between protocol and generic type

I have this Container protocol

protocol Container {
    associatedtype State : Any
    associatedtype Effect : Any
    associatedtype Intent : Any
    
    var store: Store { get }
}

And also this protocol Store

protocol Store {
    associatedtype State : Any
    associatedtype Effect : Any

    var state: AnyPublisher<State, Never> { get }
    var effect: AnyPublisher<Effect, Never> { get }
}

I want my store in Container to be type of Store<State, Effect> where State and Effect are the same as specified in the Container protocol. But I don't get it how, compiler ask me to use any Store but it erase typing.

And also, with any, if I try to subscribe on the state publisher, I got an error : Member 'state' cannot be used on value of type 'any Store'; consider using a generic constraint instead

I tried to do this

protocol Container {
    associatedtype State : Any
    associatedtype Effect : Any
    associatedtype Intent : Any
    
    var store: Store<State, Effect> { get }
    
    func post(intent: Intent)
}

But again compiler complains that Protocol 'Store' does not have primary associated types that can be constrained

I really don't understand how achieve what I want.

Sorry if it is a basic question, I'm a originally a Kotlin developer. To be clear, in Kotlin, it would be this if it could help :

interface Container<State : Any, Effect : Any, Intent : Any> {
    val store: Store<State, Effect>

    fun post(intent: Intent)
}

Hope someone could help me ! Thanks

2

There are 2 best solutions below

0
Sweeper On

As one of the error messages say, you can add primary associated types to Store to make Store<State, Effect> possible.

protocol Store<State, Effect> {
    associatedtype State
    associatedtype Effect

    var state: AnyPublisher<State, Never> { get }
    var effect: AnyPublisher<Effect, Never> { get }
}

protocol Container {
    associatedtype State
    associatedtype Effect
    associatedtype Intent
    
    var store: any Store<State, Effect> { get }
    
    func post(intent: Intent)
}

Now it is possible to subscribe to state/effect:

func foo<T: Container>(container: T) {
    container.store.effect.sink { effect in
        // ...
    }
}

You seem to be translating what you would have written in Kotlin, "word for word", into Swift. This is not a good idea, both for programming languages and for natural languages. Kotlin interfaces and Swift protocols have very different semantics and capabilities. Yes, they both declare requirements for implementing types, but that's where the similarity stops. You will likely run into problems later down the line.

Unlike in Kotlin, where the : Any constraint forces the type arguments to be non-nullable, : Any in Swift is redundant. SomeType? (aka Optional<SomeType>) in Swift might "look like" a nullable type in the Kotlin sense, but it has very different semantics. There usually isn't a good reason to constrain a type parameter to be non-Optional.

0
Olex On

To keep the solution truly generic you need to specialise Store type used in Container:

protocol Store<State, Effect> {
    associatedtype State
    associatedtype Effect

    var state: AnyPublisher<State, Never> { get }
    var effect: AnyPublisher<Effect, Never> { get }
}

protocol Container<State, Effect> {
    associatedtype State
    associatedtype Effect
    associatedtype StoreType: Store<State, Effect>
    associatedtype Intent

    var store: StoreType { get }

    func post(intent: Intent)
}