3

Consider these protocols

protocol NamedThing{
    var name:String{ get }
}

protocol ValuedThing{

    associatedtype ValueType

    var value:ValueType{ get }
}

And these structs...

struct TestThingA : NamedThing {
    let name = "TestThing"
}

struct TestThingB : ValuedThing {

    typealias ValueType = Int

    let value = 4
}

struct TestThingC : NamedThing, ValuedThing {

    typealias ValueType = Int

    let name = "TestThing"
    let value = 4
}

I'm trying to write an extension that would only apply to struct TestThingC because it adheres to both protocols.

None of these work, of course...

extension NamedThing & ValuedThing{
    func test(){
        print("Named thing \(name) has a value of \(value)")
    }
}

extension Any where NamedThing & ValuedThing {
    func test(){
        print("Named thing \(name) has a value of \(value)")
    }
}

extension Any where Self is NamedThing, Self is ValuedThing{

    func test(){
        print("Named thing \(name) has a value of \(value)")
    }
}

extension Any where Self == NamedThing, Self == ValuedThing{

    func test(){
        print("Named thing \(name) has a value of \(value)")
    }
}

So how does one write an extension that applies to items which adhere to both (multiple) protocols?

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286

2 Answers2

4

You could define a restricted extension to one of the protocols:

extension NamedThing where Self: ValuedThing {
    func test(){
        print("Named thing \(name) has a value of \(value)")
    }
}

TestThingC().test() // compiles

TestThingA().test() // error: Type 'TestThingA' does not conform to protocol 'ValuedThing'
TestThingB().test() // error: Value of type 'TestThingB' has no member 'test'
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 2
    Yup. And even though this is technically an extension to only one of the protocols, it's in effect an extension to the combination — that is, you get the same results as if you write `extension ValuedThing where Self: NamedThing`. – rickster Dec 19 '17 at 21:49
  • I actually originally thought to simply extend `Any` with two where clauses, but the compiler didn't like that, which led me down my path. It's funny... I didn't even consider extending one protocol, then using the other(s) in the where clause which would've done the exact same thing. And like @rickster said, the order doesn't matter. Really neat! Accepted! – Mark A. Donohoe Dec 20 '17 at 16:17
1

I have always solved these issues by creating a new protocol that inherits from the other protocols.

protocol NamedValueThing: NamedThing, ValuedThing { }
extension TestThingC: NamedValueThing { }

extension NamedValueThing {
    func test() {
        print("\(value) \(name)")
    }
}

//TestThingA().test() //error: value of type 'TestThingA' has no member 'test'
//TestThingB().test() //error: value of type 'TestThingB' has no member 'test'
TestThingC().test() //4 TestThing
Yannick
  • 3,210
  • 1
  • 21
  • 30
  • That was my initial thought, but that doesn't actually solve the issue as while things that implement NamedValueThing will implicitly implement NamedThing and ValuedThing, things that implement NamedThing and ValuedThing will *not* implicitly pick up NamedValueThing, meaning you have to manually apply that meta-protocol. I'm trying to do the reverse... define things on *any* item that implements those two protocols, not just ones I've manually tagged. Make sense? – Mark A. Donohoe Dec 20 '17 at 16:13
  • Yes, unfortunately, the downside of this method is that you explicitly have to conform to the new protocol. I understood your question, I just didn't see how that was possible. Personally, I don't think it's a big effort to extend the type to conform to the new protocol. But I also would be interested in a way to have the compiler figure this out. – Yannick Dec 20 '17 at 22:38
  • Unfortunately, we don't know all the types that are being handed to us. We just know they conform to those two protocols. And actually, as @MartinR above showed, it's pretty easy. You extend one of the protocols with a 'where' clause that says it also has to conform to the other(s). Doesn't matter which order since the requirement is the same. Pretty neat trick if you ask me! – Mark A. Donohoe Dec 20 '17 at 22:55