I've been scratching my head for the past few days over protocols / dynamic dispatch behaviour I'm observing. It's not easy to explain the entire setup so I've distilled my code to the bare minimum that still reproduces the issue.
Here's the basic setup:
protocol Foo {
associatedtype T
var foo: T { get }
}
protocol Bar {
var bar: String { get }
}
extension Bar where Self: Foo {
var bar: String { "I'm foo" }
}
extension Bar where Self: Foo, Self.T: Numeric {
var bar: String { "Deep down I'm a number" }
}
Since bar is declared in protocol requirements I expect it to be dynamically dispatched to bar properties.
Here's a snippet that tests this assumption:
struct PlainFoo: Foo, Bar {
var foo = "Hello"
}
struct IntFoo: Foo, Bar {
var foo: Int = 3
}
let plainFoo = PlainFoo()
let intFoo = IntFoo()
print(plainFoo.bar) // "I'm foo"
print(intFoo.bar) // "Deep down I'm a number"
let bars: [Bar] = [plainFoo, intFoo]
bars.forEach { print($0.bar) }
// prints:
// "I'm foo"
// "Deep down I'm a number"
And it is in fact correctly dispatched! Value of bar is correct both when I reference my object by their type as well as when I call bar from a collection of [Bar] type.
Now things do not work the same way with generics:
struct GenericFoo<V>: Foo, Bar {
var foo: V
}
let genericFooString = GenericFoo(foo: "Hello")
let genericFooInt = GenericFoo<Int>(foo: 3)
// Correct dynamic dispatch to bar:
print(genericFooString.bar) // "I'm foo"
print(genericFooInt.bar) // "Deep down I'm a number"
// Things go wrong here:
let genericBars: [Bar] = [genericFooString, genericFooInt]
genericBars.forEach { print($0.bar) }
// prints:
// "I'm foo"
// "I'm foo" <- should print "Deep down I'm a number" instead!
Although bar is correctly called when calling it on an object, calling bar when I iterate over a collection of [Bar] doesn't dispatch the call as I would expect based on all the previous examples.
Can anyone help me understand what's going on? How can I make Swift call correct implementation of bar in that last example?
Thanks for your reply @Slava_Pestov! I read up on double dispatch pattern and although I don't know how to apply it to my example yet I tried implementing it as per wikipedia examples and I've hit an issue I'd like to understand before I continue.
Here's my setup:
protocol Foo {
func bar(using provider: Bar) -> String
}
extension Foo {
func bar(using provider: Bar) -> String {
provider.bar(for: self)
}
}
extension Foo where Self: Buz {
func bar(using provider: Bar) -> String {
provider.bar(for: self) // <--- breakpoint 1
}
}
// ---
protocol Bar {
func bar(for: some Foo) -> String // same behaviour when declared as func bar<T: Foo>(for: T) -> String
}
extension Bar {
// Implementation for any Foo
func bar(for: some Foo) -> String {
"plain foo"
}
// Special treatment for Foo that also conforms to Buz
func bar<T: Foo>(for: T) -> String where T:Buz {
"buz foo" // <--- breakpoint 2
}
}
// ---
protocol Buz {} // Special treatment when computing bar
// ----
struct Plain: Foo {}
struct Complex: Foo, Buz {}
struct SomeBar: Bar {}
let plain = Plain()
let complex = Complex()
let someBar = SomeBar()
let elements: [Foo] = [plain, complex]
elements.forEach {
print($0.bar(using: someBar)) // prints "plain foo" twice.
}
The first part of "double dispatch" pattern works correctly - I'm hitting the breakpoint 1 (see comment in code), however breakpoint 2 is never hit, both calls to func bar end up in the implementation that returns "plain foo".
Can you please help me understand if what I'm trying to do here is possible?
The only way to get "buz foo" returned I found was to add following method to Buz:
Discussing this with Foo's and Bar's is getting a bit confusing - I'm happy to provide more of a "real world" context for what I'm trying to achieve if you think would be helpful.