I have a protocol with PAT that provides a default implementation in an extension. Then, a class conforming that protocol provides an "override". This override is not called, and the compiler "prefers" the default.
//---- Definition of the protocol with a default implentation, because I am forced to.
protocol Bag: AnyObject {
associatedtype BagObject
func add(_ e: BagObject)
}
extension Bag where BagObject: Equatable {
// I here give a default implementation, because I can't do differently, as this
// is an extension, and it is an extension because it only applies to assoicated
// types that are Equatables
func contains(_ e: BagObject) -> Bool {
print("Default implementation is called")
return false
}
}
///---- Definition of a class that gives a concrete implementation of the protocol
class BagConcreteImplementation<BagObject>: Bag {
var bag = Array<BagObject>()
func add(_ e: BagObject) { bag.append(e) }
}
extension BagConcreteImplementation where BagObject: Equatable {
// I here give a concrete implementation when the generic type is Equatable
func contains(_ e: BagObject) -> Bool {
print("Concrete implementation is called")
return bag.contains(e)
}
}
///---- This is a class that encapsulate a bag, in real life, this class is adding some filtering to objects that can be added to the bag
class AClassThatHaveABag<BagType: Bag> {
typealias BagObject = BagType.BagObject
let myBag: BagType
init(bag: BagType) { myBag = bag }
}
extension AClassThatHaveABag where BagType.BagObject: Equatable {
func contains(_ e: BagObject) {
// The problem here is that the compiler only knows that myBag is a Bag
// of Int and therefore calls the default implementation
// It does not call the concrete implementation of the concrete class that WILL be provided
myBag.contains(e)
}
}
let aBagOfInt = BagConcreteImplementation<Int>()
let objectThatContainsABagOfInt = AClassThatHaveABag(bag: aBagOfInt)
aBagOfInt.contains(0)
objectThatContainsABagOfInt.contains(0)
// Prints
// Concrete implementation is called
// Default implementation is called
We can clearly see the direct call is call the concrete implementation, while the encapsulated call is calling the default implementation.
How to do to make sure the concrete implementation is always called, even through an encapsulation ?
I tried a few things, and somehow this works:
Declare contains
in the protocol, not in an extension, like this:
func contains<T>(_ e: T) -> Bool where T: Equatable, T == BagObject
If you do things like T == BagObject
in a class, a compiler error will show up saying that this makes T
redundant, but apparently this is okay in a protocol.
Then, implement the default implementation like this:
extension Bag {
func contains<T>(_ e: T) -> Bool where T: Equatable, T == BagObject {
print("Default implementation is called")
return false
}
}
Then, you can just implement the concrete implementation directly in the class, as a non-generic method.
class BagConcreteImplementation<BagObject> : Bag {
func contains(_ e: BagObject) -> Bool where BagObject : Equatable {
print("Concrete implementation is called")
return bag.contains(e)
}
var bag = Array<BagObject>()
func add(_ e: BagObject) { bag.append(e) }
}
Without changing the call site, the code would print:
Concrete implementation is called
Concrete implementation is called
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments