0

I am attempting something very tricky using protocols provided by the standard library. Basically, in the following code, A and B are protocols from the standard library. I want to implement a "special" kind of B (named C in the following code), that is more clever than a regular B, and I want to make an implementation of A that can use either a B or a C, and that would use a default behaviour when provided with a B, and a smarter behaviour when provided with a C. However, C also by necessity uses Self.

This code is what I would like to do, but it does not work. It can be used as a playground for experimentation.

protocol A {
    associatedtype T: B
    func lookAt(thing: B) -> String
}

protocol B {
}

protocol C: B {
    static var someThing: Self { get }
}

extension B {
    func beSmart() -> String { return "I am a stupid B" }
}

extension C {
    func beSmart() -> String { return "I am a clever C" }
}

struct AImplementation<T: B> {
    func lookAt(thing: T) -> String {
        return "A \(String(describing: T.self)) says: \(thing.beSmart())"
    }
}

struct BImplementation: B {
}

struct CImplementation: C {
    static let someThing = CImplementation()
}

let AimplWithB = AImplementation<BImplementation>()
AimplWithB.lookAt(thing: BImplementation())

let AimplWithC = AImplementation<CImplementation>()
AimplWithC.lookAt(thing: CImplementation())

This should result in the texts "A BImplementation says: I am a stupid B" and "A CImplementation says: I am a clever C".

However, it will currently say "A BImplementation says: I am a stupid B" and "A CImplementation says: I am a stupid B".

The correct type is available at the call to beSmart(), but apparently Swift will not figure out which beSmart() to call. Is there some way to make this code work as intended, without touching protocols A or B, and letting C use Self?

Dag Ågren
  • 1,064
  • 7
  • 18
  • Not possible. See https://stackoverflow.com/questions/31431753/swift-protocol-extensions-overriding – aaron Oct 15 '17 at 16:30
  • Swift generics aren't like C++ templates, compare https://stackoverflow.com/q/41980001/2976878. Overload resolution for `beSmart()` happens in `lookAt(thing:)`. From there, the only thing the compiler knows about `T` is that it's also a `B`. Thus the implementation in the `B` extension is called. – Hamish Oct 15 '17 at 16:35
  • Actually, it was possible as I first stated it. I could just cast the B to a C and get the behaviour I wanted. However, this was only because I simplified my example too much. I updated the code now to show that C also uses Self, which prevents simple casting to C. – Dag Ågren Oct 15 '17 at 16:37
  • Again, I can't change A or B, they are standard library protocols. – Dag Ågren Oct 15 '17 at 16:38
  • @DagÅgren I suspect that type-casting probably isn't the best solution to your problem (but without knowing what that concrete problem is; it's impossible to say). `C` really *should* be fully usable as a type (the use of `Self` in a return position can be covariant), the compiler just doesn't support it yet. As a workaround you could define a second underscored protocol with `func beSmart() -> String` as a requirement (thus dispatching dynamically via protocol witness table). E.g http://swift.sandbox.bluemix.net/#/repl/59e392466cbea87f72c47128 – Hamish Oct 15 '17 at 16:55
  • That might indeed be a workable solution. I'll do some testing to see if it works in the actual case. – Dag Ågren Oct 15 '17 at 17:53
  • Actually, no, it won't work. I need to access the Self-typed value (in the real case, a [Self: Int] dictionary) in the "clever" case. (What I am trying to do is extend CodingKey with a [Self: Int] dictionary so I can have both stringValues and intValues in my Encoder and Decoder, yet still use the autogenerated Codable functions.) – Dag Ågren Oct 15 '17 at 18:08
  • Correction: It does work! I just have to do things in just the right order. Thanks! If you want to be marked as a correct answer, I guess repost that as an answer. – Dag Ågren Oct 15 '17 at 18:19

1 Answers1

-1

I believe the compiler doesn't have enough information to find the path to the beSmart() implementations because it is not in B protocol's exposed methods.

If you add it to the protocol, your extensions will "connect" and also act as default implementations so that the classes that implement the protocol don't have to define the function.

protocol B {
 func beSmart() -> String
}
Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • As I said, A and B are standard library protocols and can't be changed. Hamish posted the correct solution in a comment to the original post. – Dag Ågren Oct 18 '17 at 07:48