多次调度 Swift 尝试:通用参数 'T' 的参数冲突('Cat' 与 'Dog')
Multiple Dispatch Swift attempt: Conflicting arguments to generic parameter 'T' ('Cat' vs. 'Dog')
我正在观看关于 Julia 的关于多重调度的视频,并且很好奇我是否可以在 Swift 中编写类似的东西。我看到 Swift 依赖于 Julia 似乎在 运行 时间确定类型的编译器,但我也发现了一些我不理解的东西 Swift.
为什么下面的函数 encounters
在 2 个参数是同一类型的 Pet 时有效,但当一个是 Cat 另一个是 Dog 时却无效?
例如下面的两个函数工作
encounters(Arturo, Gabby) // both Cat
encounters(Cc, Bb) // both Dog
但是这些会导致编译器错误
Conflicting arguments to generic parameter 'T' ('Cat' vs. 'Dog')
encounters(Arturo, Bb) // Cat and Dog
encounters(Bb, Arturo) // Dog and Cat
protocol Pet: Equatable {
var name: String { get }
}
struct Cat: Pet {
let name: String
}
struct Dog: Pet {
let name: String
}
let Arturo = Cat(name: "Arturo")
let Gabby = Cat(name: "Gabby")
let Bb = Dog(name: "Bb")
let Cc = Dog(name: "Cc")
func encounters<T: Pet>(_ a: T, _ b: T) {
var verb: String
switch (a, b) {
case is (Cat, Dog):
verb = meet(a as! Cat, b as! Dog)
case is (Dog, Dog):
verb = meet(a as! Dog, b as! Dog)
case is (Cat, Cat):
verb = meet(a as! Cat, b as! Cat)
case is (Dog, Cat):
verb = meet(a as! Dog, b as! Cat)
default:
fatalError()
}
print("\(a.name) meets \(b.name) and \(verb)")
}
func meet(_ a: Cat, _ b: Cat) -> String {
return "Slinks"
}
func meet(_ a: Cat, _ b: Dog) -> String {
return "Hisses"
}
func meet(_ a: Dog, _ b: Dog) -> String {
return "Howles"
}
func meet(_ a: Dog, _ b: Cat) -> String {
return "Barks"
}
如果您要创建一个类型符合协议的通用函数,那么您需要一直使用该协议,您不能在函数内部使用符合类型,因为可以创建任何自定义类型遵守协议。
这是一个仅使用协议的示例解决方案
protocol Pet {
var name: String { get }
var same: String { get }
var other: String { get }
}
符合协议的猫狗
struct Cat: Pet {
var name: String
var other: String { "Slinks" }
var same: String { "Hisses" }
}
struct Dog: Pet {
let name: String
var other: String { "Barks" }
var same: String { "Howles" }
}
然后函数变成
func encounters<T: Pet, U: Pet>(_ a: T, _ b: U) {
let verb: String
if T.self == U.self {
verb = a.same
} else {
verb = a.other
}
print("\(a.name) meets \(b.name) and \(verb)")
}
例子
let arturo = Cat(name: "Arturo")
let gabby = Cat(name: "Gabby")
let bb = Dog(name: "Bb")
let cc = Dog(name: "Cc")
encounters(arturo, bb)
encounters(bb, arturo)
encounters(gabby, arturo)
encounters(cc, bb)
Arturo meets Bb and Slinks
Bb meets Arturo and Barks
Gabby meets Arturo and Hisses
Cc meets Bb and Howles
我不会采用这种方法,但它不起作用的原因是你将 Equatable 附加到 Pet。您的意思可能是“宠物应该与其他宠物具有可比性”之类的意思,但那不是那个意思。这意味着符合 Pet 的类型本身必须是 Equatable。如果你删除 Equatable,这就像克劳斯所说的那样没有通用的写法:
protocol Pet { ... }
func encounters(_ a: Pet, _ b: Pet) { ... }
这么多 as!
让 Swift 开发者感到紧张,这是不必要的。 as!
没有错,但它为编译器无法捕获的简单错误打开了大门。这种风格让编译器帮助你多一点。它仍然无法捕获丢失的案例。
func encounters(_ a: Pet, _ b: Pet) {
var verb: String
switch (a, b) {
case let (a as Cat, b as Dog):
verb = meet(a, b)
case let (a as Dog, b as Dog):
verb = meet(a, b)
case let (a as Cat, b as Cat):
verb = meet(a, b)
case let (a as Dog, b as Cat):
verb = meet(a, b)
default:
fatalError()
}
print("\(a.name) meets \(b.name) and \(verb)")
}
我绝对不喜欢这里的fatalError
。如果创建了一些新的 Pet,这段代码和我假设您编写的 Julia 都会崩溃。那不是很好。但我认为这不是这个问题的主要部分。在 Swift 中,您会在默认分支中放置一些内容。在 Julia 中,您将为 meet(a::Pet, b::Pet)
.
添加一个更通用的多方法
如果真的只有两种可能的宠物,那么您真的应该考虑枚举而不是动态调度,但我再次认为这不是问题所在。
就像我说的,我不太喜欢这种方法。我认为它非常冗长,并且增加了很多错误的地方。如果你想要这种动态类型查找,我会在数据中进行动态类型查找。
struct Meeting {
let lhs: Pet.Type
let rhs: Pet.Type
let verb: String
func matches(_ lhs: Pet, _ rhs: Pet) -> Bool {
self.lhs == type(of: lhs) && self.rhs == type(of: rhs)
}
}
let meetings = [
Meeting(lhs: Cat.self, rhs: Dog.self, verb: "Hisses"),
Meeting(lhs: Dog.self, rhs: Dog.self, verb: "Howles"),
Meeting(lhs: Cat.self, rhs: Cat.self, verb: "Slinks"),
Meeting(lhs: Dog.self, rhs: Cat.self, verb: "Barks"),
]
func encounters(_ a: Pet, _ b: Pet) {
let verb = meetings
.first(where: { [=12=].matches(a, b) })?.verb
?? "passes by"
print("\(a.name) meets \(b.name) and \(verb)")
}
即使您需要函数,也可以将其扩展为将函数存储为数据(这是实现动态调度的一种非常强大的方式)。
不只是一个动词,而是向会议添加一个动作功能:
struct Meeting {
let lhs: Pet.Type
let rhs: Pet.Type
let action: (Pet) -> String // Function that takes a Pet and gives a String
func matches(_ lhs: Pet, _ rhs: Pet) -> Bool {
self.lhs == type(of: lhs) && self.rhs == type(of: rhs)
}
}
现在它开始看起来更像您的 multi-methods,将所有专用逻辑放在一个地方:
let meetings = [
Meeting(lhs: Cat.self, rhs: Dog.self, action: { "Hisses at \([=14=].name)" }),
Meeting(lhs: Dog.self, rhs: Dog.self, action: { "Howles at \([=14=].name)" }),
Meeting(lhs: Cat.self, rhs: Cat.self, action: { _ in "Slinks by" }),
Meeting(lhs: Dog.self, rhs: Cat.self, action: { "Barks at \([=14=].name)" }),
]
并且encounters
称它为:
func encounters(_ a: Pet, _ b: Pet) {
let action = meetings
.first(where: { [=15=].matches(a, b) })?.action
?? { _ in "passes by" }
let verb = action(b)
print("\(a.name) meets \(b.name) and \(verb)")
}
它并不完全像 multimethods 那样强大,但在 compile-time.
处更容易推理类型安全性
我正在观看关于 Julia 的关于多重调度的视频,并且很好奇我是否可以在 Swift 中编写类似的东西。我看到 Swift 依赖于 Julia 似乎在 运行 时间确定类型的编译器,但我也发现了一些我不理解的东西 Swift.
为什么下面的函数 encounters
在 2 个参数是同一类型的 Pet 时有效,但当一个是 Cat 另一个是 Dog 时却无效?
例如下面的两个函数工作
encounters(Arturo, Gabby) // both Cat
encounters(Cc, Bb) // both Dog
但是这些会导致编译器错误
Conflicting arguments to generic parameter 'T' ('Cat' vs. 'Dog')
encounters(Arturo, Bb) // Cat and Dog
encounters(Bb, Arturo) // Dog and Cat
protocol Pet: Equatable {
var name: String { get }
}
struct Cat: Pet {
let name: String
}
struct Dog: Pet {
let name: String
}
let Arturo = Cat(name: "Arturo")
let Gabby = Cat(name: "Gabby")
let Bb = Dog(name: "Bb")
let Cc = Dog(name: "Cc")
func encounters<T: Pet>(_ a: T, _ b: T) {
var verb: String
switch (a, b) {
case is (Cat, Dog):
verb = meet(a as! Cat, b as! Dog)
case is (Dog, Dog):
verb = meet(a as! Dog, b as! Dog)
case is (Cat, Cat):
verb = meet(a as! Cat, b as! Cat)
case is (Dog, Cat):
verb = meet(a as! Dog, b as! Cat)
default:
fatalError()
}
print("\(a.name) meets \(b.name) and \(verb)")
}
func meet(_ a: Cat, _ b: Cat) -> String {
return "Slinks"
}
func meet(_ a: Cat, _ b: Dog) -> String {
return "Hisses"
}
func meet(_ a: Dog, _ b: Dog) -> String {
return "Howles"
}
func meet(_ a: Dog, _ b: Cat) -> String {
return "Barks"
}
如果您要创建一个类型符合协议的通用函数,那么您需要一直使用该协议,您不能在函数内部使用符合类型,因为可以创建任何自定义类型遵守协议。
这是一个仅使用协议的示例解决方案
protocol Pet {
var name: String { get }
var same: String { get }
var other: String { get }
}
符合协议的猫狗
struct Cat: Pet {
var name: String
var other: String { "Slinks" }
var same: String { "Hisses" }
}
struct Dog: Pet {
let name: String
var other: String { "Barks" }
var same: String { "Howles" }
}
然后函数变成
func encounters<T: Pet, U: Pet>(_ a: T, _ b: U) {
let verb: String
if T.self == U.self {
verb = a.same
} else {
verb = a.other
}
print("\(a.name) meets \(b.name) and \(verb)")
}
例子
let arturo = Cat(name: "Arturo")
let gabby = Cat(name: "Gabby")
let bb = Dog(name: "Bb")
let cc = Dog(name: "Cc")
encounters(arturo, bb)
encounters(bb, arturo)
encounters(gabby, arturo)
encounters(cc, bb)
Arturo meets Bb and Slinks
Bb meets Arturo and Barks
Gabby meets Arturo and Hisses
Cc meets Bb and Howles
我不会采用这种方法,但它不起作用的原因是你将 Equatable 附加到 Pet。您的意思可能是“宠物应该与其他宠物具有可比性”之类的意思,但那不是那个意思。这意味着符合 Pet 的类型本身必须是 Equatable。如果你删除 Equatable,这就像克劳斯所说的那样没有通用的写法:
protocol Pet { ... }
func encounters(_ a: Pet, _ b: Pet) { ... }
这么多 as!
让 Swift 开发者感到紧张,这是不必要的。 as!
没有错,但它为编译器无法捕获的简单错误打开了大门。这种风格让编译器帮助你多一点。它仍然无法捕获丢失的案例。
func encounters(_ a: Pet, _ b: Pet) {
var verb: String
switch (a, b) {
case let (a as Cat, b as Dog):
verb = meet(a, b)
case let (a as Dog, b as Dog):
verb = meet(a, b)
case let (a as Cat, b as Cat):
verb = meet(a, b)
case let (a as Dog, b as Cat):
verb = meet(a, b)
default:
fatalError()
}
print("\(a.name) meets \(b.name) and \(verb)")
}
我绝对不喜欢这里的fatalError
。如果创建了一些新的 Pet,这段代码和我假设您编写的 Julia 都会崩溃。那不是很好。但我认为这不是这个问题的主要部分。在 Swift 中,您会在默认分支中放置一些内容。在 Julia 中,您将为 meet(a::Pet, b::Pet)
.
如果真的只有两种可能的宠物,那么您真的应该考虑枚举而不是动态调度,但我再次认为这不是问题所在。
就像我说的,我不太喜欢这种方法。我认为它非常冗长,并且增加了很多错误的地方。如果你想要这种动态类型查找,我会在数据中进行动态类型查找。
struct Meeting {
let lhs: Pet.Type
let rhs: Pet.Type
let verb: String
func matches(_ lhs: Pet, _ rhs: Pet) -> Bool {
self.lhs == type(of: lhs) && self.rhs == type(of: rhs)
}
}
let meetings = [
Meeting(lhs: Cat.self, rhs: Dog.self, verb: "Hisses"),
Meeting(lhs: Dog.self, rhs: Dog.self, verb: "Howles"),
Meeting(lhs: Cat.self, rhs: Cat.self, verb: "Slinks"),
Meeting(lhs: Dog.self, rhs: Cat.self, verb: "Barks"),
]
func encounters(_ a: Pet, _ b: Pet) {
let verb = meetings
.first(where: { [=12=].matches(a, b) })?.verb
?? "passes by"
print("\(a.name) meets \(b.name) and \(verb)")
}
即使您需要函数,也可以将其扩展为将函数存储为数据(这是实现动态调度的一种非常强大的方式)。
不只是一个动词,而是向会议添加一个动作功能:
struct Meeting {
let lhs: Pet.Type
let rhs: Pet.Type
let action: (Pet) -> String // Function that takes a Pet and gives a String
func matches(_ lhs: Pet, _ rhs: Pet) -> Bool {
self.lhs == type(of: lhs) && self.rhs == type(of: rhs)
}
}
现在它开始看起来更像您的 multi-methods,将所有专用逻辑放在一个地方:
let meetings = [
Meeting(lhs: Cat.self, rhs: Dog.self, action: { "Hisses at \([=14=].name)" }),
Meeting(lhs: Dog.self, rhs: Dog.self, action: { "Howles at \([=14=].name)" }),
Meeting(lhs: Cat.self, rhs: Cat.self, action: { _ in "Slinks by" }),
Meeting(lhs: Dog.self, rhs: Cat.self, action: { "Barks at \([=14=].name)" }),
]
并且encounters
称它为:
func encounters(_ a: Pet, _ b: Pet) {
let action = meetings
.first(where: { [=15=].matches(a, b) })?.action
?? { _ in "passes by" }
let verb = action(b)
print("\(a.name) meets \(b.name) and \(verb)")
}
它并不完全像 multimethods 那样强大,但在 compile-time.
处更容易推理类型安全性