How would I define a protocol with two associatedtype's which conform to another associatedtype

I have a protocol defined like this:

protocol Abstraction {

    associatedtype T

    associatedtype On: T
    associatedtype Off: T

}

extension Abstraction {

    var isOn: Bool {
        ....
    }

    var abstraction: T {
        return isOn ? on : off 
    }

}

I want to assert that On & Off both conform to the same protocol, the above does not work because it cannot determine that T is of type protocol.

Is this something like this possible?


I can work around it by not providing any T conformance and implementing it as an extension with the concrete types, but this is annoying and has to be repeated for every type even though the core logic is the same:

extension Abstraction where On: Printer, Off: Printer {
    var abstraction: Printer {
        return isOn ? on : off 
    }
}

If you make Printer and every other of those types conform to SomeProtocol, then you could of course just write:

protocol Abstraction {
    associatedtype On: SomeProtocol
    associatedtype Off: SomeProtocol
}

But I have a feeling that I don't understand what you want to achieve.

Here's a more concrete example, I want to guarantee that both On and Off conform to the same protocol so I am able to have the property var abstract: Abstraction which avoids me putting if isOn throughout the code. Hope this clears up your understanding.

import Foundation

protocol Abstraction {
    
    associatedtype Abstraction
    
    associatedtype On
    associatedtype Off
    
    var isOn: Bool { get set }
    var key: String { get }
    
    var on: On { get }
    var off: Off { get }
    
}

extension Abstraction {
    
    var key: String {
        return String(describing: self)
    }
    
    var isOn: Bool {
        get { return UserDefaults.standard.bool(forKey: key) }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
    
    var abstract: Abstraction { // <-- To avoid littering `if isOn` throughout
        return isOn ? on : off
    }
    
}

protocol Printer {
    func print(_ string: String)
}

struct SwiftPrinter: Printer {
    func print(_ string: String) {
        Swift.print(string)
    }
}

struct LoggerPrinter: Printer {
    func print(_ string: String) {
        Logger.log(string)
    }
}

class PrinterAbstraction: Printer, Abstraction {
    
    typealias Abstraction = Printer
    
    typealias On = SwiftPrinter
    typealias Off = LoggerPrinter
    
    lazy var on: On = .init()
    lazy var off: Off = .init()
    
    func print(_ string: String) {
        abstract.print(string)
    }
    
}

AFAICS the protocol Abstraction really has no use for any more specific type than the protocol/class of the associated type that you (somewhat confusingly also) call Abstraction, ie on and off don't need their separate concrete types represented there, they can simply both have their common supertype Abstraction, because they are never used as anything more specific than Abstraction. So:

protocol AbstractionSwitch { // <-- I don't know about this renaming-attempt but anyway
    associatedtype Abstraction
    var on: Abstraction
    var off: Abstraction
    ...
}

Note that on and off can still be different types (dynamically), as long as they can both be stored as their common supertype Abstraction.

I rewrote your last example into a working program, using a generic struct AbstractionSwitch<Abstraction> instead of your protocol Abstraction:


import Foundation


struct AbstractionSwitch<Abstraction> {
    var on: Abstraction
    var off: Abstraction
    var key: String

    var isOn: Bool {
        get { return UserDefaults.standard.bool(forKey: key) }
        set { UserDefaults.standard.set(newValue, forKey: key) }
    }
    var abstraction: Abstraction { // <-- To avoid littering `if isOn` throughout
        return isOn ? on : off
    }

    init(key: String, on: Abstraction, off: Abstraction) {
        self.key = key
        self.on = on
        self.off = off
    }

}

struct FakeLogger {
    static func log(_ str: String) { print("fakelogged:", str) }
}

protocol Printer {
    func print(_ string: String)
}

struct SwiftPrinter: Printer {
    func print(_ string: String) {
        Swift.print(string)
    }
}

struct LoggerPrinter: Printer {
    func print(_ string: String) {
        FakeLogger.log(string)
    }
}

var a = AbstractionSwitch<Printer>(key: "printer",
                                   on: SwiftPrinter(),
                                   off: LoggerPrinter())
a.isOn = true
a.abstraction.print("Hello world!") // Hello world!
a.isOn = false
a.abstraction.print("Hello world!") // fakelogged: Hello world!

Don't know if I still misunderstood your goal.

Don't know if I still misunderstood your goal.

Not at all, the above example you provided is something I neglected to consider (I assumed I could do it all using protocols) - but it looks like that will solve my dilemma - thanks for the help.

Ah, nice. I'm sure it's possible to come up with an even simpler design.

I've often found myself arriving at an overly complex solution—or even no solution at all—by being a bit too protocol oriented. : )

I'm sure it's possible to come up with an even simpler design.

Actually, It will probably be possible once I move to swift 5.1 with Opaque Result Types.