1

我正在观看有关 Julia 的有关 Multiple Dispatch 的视频,并且很好奇我是否可以在 Swift 中编写类似的东西。我看到 Swift 依赖于 Julia 似乎在运行时确定类型的编译器,但我也发现了一些我对 Swift 不了解的地方。

为什么encounters当 2 个参数属于同一类型的 Pet 时,下面的函数会起作用,但当一个是 Cat 而另一个是 Dog 时却不行?

例如下面的两个函数起作用

encounters(Arturo, Gabby) // both Cat
encounters(Cc, Bb) // both Dog

但是这些给出了编译器错误

泛型参数“T”的参数冲突(“Cat”与“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"
}
4

2 回答 2

4

如果您要创建具有符合协议的类型的泛型函数,那么您需要一直使用该协议,您不能在函数内部使用符合类型,因为可以使任何自定义类型符合协议.

这是仅使用协议的示例解决方案

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 遇见 Bb 和 Slinks
Bb 遇见 Arturo 和 Barks
Gabby 遇见 Arturo 和 Hisses
Cc 遇见 Bb 和 Howles

于 2020-10-11T14:35:07.687 回答
3

我不会采用这种方法,但它不起作用的原因是因为您将 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).

如果真的只有两个可能的 Pets,那么你真的应该考虑枚举而不是动态调度,但我再次认为这不是问题。

就像我说的,我不太喜欢这种方法。我认为它非常冗长,并且增加了很多错误的地方。如果你想要这种动态类型查找,我会在数据中进行动态类型查找。

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: { $0.matches(a, b) })?.verb
        ?? "passes by"
    print("\(a.name) meets \(b.name) and \(verb)")
}

即使您需要函数,也可以将其扩展为将函数存储为数据(这是实现动态调度的一种非常强大的方式)。

不只是一个动词,向 Meeting 添加一个动作函数:

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)
    }
}

现在它开始看起来更像您的多方法,将所有专门的逻辑放在一个地方:

let meetings = [
    Meeting(lhs: Cat.self, rhs: Dog.self, action: { "Hisses at \($0.name)" }),
    Meeting(lhs: Dog.self, rhs: Dog.self, action: { "Howles at \($0.name)" }),
    Meeting(lhs: Cat.self, rhs: Cat.self, action: { _ in "Slinks by" }),
    Meeting(lhs: Dog.self, rhs: Cat.self, action: { "Barks at \($0.name)" }),
]

encounters称之为:

func encounters(_ a: Pet, _ b: Pet) {
    let action = meetings
        .first(where: { $0.matches(a, b) })?.action
        ?? { _ in "passes by" }

    let verb = action(b)
    
    print("\(a.name) meets \(b.name) and \(verb)")
}

它并不完全像多方法那样强大,但在编译时更容易推理类型安全。

于 2020-10-11T16:54:49.067 回答