A lot just happened. I was thinking at first about something like this:
protocol KitchenItem {
var name: String { get }
}
extension KitchenItem: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(self.name)
}
}
struct Knife: KitchenItem, Hashable {
//assume that we live in perfect world where
//where it is possible to have more than one customization point
var name: String
var isFrightening: Bool = true
func hash(into hasher: inout Hasher) {
hasher.combine(self.name)
hasher.combine(self.isFrightening)
}
}
struct Pan: KitchenItem {
var name: String
}
That felt fine to me at first: any type viewed as kitchen item should have its own implementation of hashability and every other descendant type may opt to supplement its own hashability scheme; until I realized that this would be actually impossible because in this hypothetical case notion of Hashable
becomes invariant.
var diverseToolset = Set<KitchenItem>([Knife(name: "Fierce Chopper"), Pan(name: "PAN-9000")])
func doTrick <C: Collection>(with collection: C) where C.Element: Hashable {
let _ = collection.allSatisfy { item in
collection.allSatisfy { $0.hashValue == item.hashValue }
}
}
doTrick (with: diverseToolset)
The code above would be broken because the hash values are not tied to a single identity (Pan uses default hashing from KitchenItem, while Knife uses its own hashing). And if in swift more powerful customization facility would ever be considered, something has to be done about variance.
Although, the code above would be perfectly fine, if it have had been explicitly set to account for such case, in other words, if it was set to use always Hashable implementation from KitchenItem, the world would be a better place.
var diverseToolset = Set<KitchenItem>([Knife(name: "Fierce Chopper"), Pan(name: "PAN-9000")])
func doTrick <C: Collection>(with collection: C)
where C.Element: Hashable..? {
//this code uses recent, most common witness from protocol hierarchy
//ignoring any overrides
collection.allSatisfy { item in
collection.allSatisfy { $0 == item }
} ? print("phew!") : print("oops...")
//now it is okay
}
doTrick (with: diverseToolset)
Thoughts?
Btw. I have this annotation in mind:
var set = Set<Hashable..?>([Pan(...), Knife(...)])
//uses most common witness from hierarchy
//(from KitchenItem)
set.insert("Fake blender") //compilation error
//String does not share common witness with ... <Pick some common type here>
//KitchenItem will do fine
var set2 = Set<?..Hashable>([Pan(...), Pan(...), Pan(...)])
//most current type within this hierarchy can only be found within
//concrete type, which is Pan in this case
set2.insert(Knife(...)) //error
//Knife most recent witness for Hashable
//is different from Pan' most recent witness for Hashable
So we can basically get Java (with a LOT of runtime dispatch and inheritance graph resolutions), and as everybody knows, java killed its type inference. Would be fine to see, what direction the core team will choose. I'd prefer becoming java though.
To reiterate
Summary
protocol Demiurge {
func demonstrateGodlyPower ()
}
extension Demiurge {
func demonstrateGodlyPower () {
print ("...")
}
}
protocol HigherGod: Demiurge {}
extension HigherGod {
func demonstrateGodlyPower () {
print ("...thunderstorm growls..")
}
}
protocol MatterTransformingGod: HigherGod {}
extension MatterTransformingGod {
func demonstrateGodlyPower () {
print ("...turning X into Y...")
}
}
struct Midas: MatterTransformingGod {
func demonstrateGodlyPower () {
print ("...turning mud into gold...")
}
}
//invariant
func invoke <T: Demiurge>(_ target: T) { target.demonstrateGodlyPower() }
invoke (Midas()) //...turning mud into gold...
invoke (Midas() as Demiurge) //...turning mud into gold...
invoke (Midas() as HigherGod) //...turning mud into gold...
invoke (Midas() as MatterTransformingGod) //...turning mud into gold...
//higher bound
func invoke <T: ?..Demiurge>(_ target: T) {}
invoke (Midas()) //...turning mud into gold...
invoke (Midas() as Demiurge) //...
invoke (Midas() as HigherGod) //...thunderstorm growls...
invoke (Midas() as MatterTransformingGod) //...turning X into Y...
//lower bound
func invoke <T: Demiurge..?>(_ target: T) {}
invoke (Midas()) //...
invoke (Midas() as Demiurge) //...
invoke (Midas() as HigherGod) //...
invoke (Midas() as MatterTransformingGod) //...