【问题标题】:Overloading generic functions in iOS Swift在 iOS Swift 中重载泛型函数
【发布时间】:2020-05-07 12:06:41
【问题描述】:

我正在尝试构建一个查找器,该查找器尝试并查找在闭包内传递给它的多种类型。

enum SomeError: Error {
    case notInitialized
}

struct TestFinder {

    func getSomething<T, U>(_ function: @escaping (T) -> U) throws -> U {
        guard let t: T = get() else {
                throw SomeError.notInitialized
        }

        return function(t)
    }

    func getSomething<T, U, V>(_ function: @escaping (T, U) -> V) throws -> V {
        guard let t: T = get(), let u: U = get() else {
                throw SomeError.notInitialized
        }

        return function(t, u)
    }

    func getSomething<T, U, V, W>(_ function: @escaping (T, U, V) -> W) throws -> W {
        guard let t: T = get(), let u: U = get(), let v: V = get() else {
                throw SomeError.notInitialized
        }

        return function(t, u, v)
    }

    private func get<T>() -> T? {
       nil
    }
}

struct UserDetails {
    let name: String
    let roll: String
}

我将查找器称为:

let testReturnType = try? TestFinder().getSomething(UserDetails.init)

编译器给我一个错误:

“getSomething”的使用不明确

此错误的原因(来自文档):

您可以通过在类型参数上提供不同的约束、要求或同时提供两者来重载泛型函数或初始化程序。当您调用重载的泛型函数或初始化程序时,编译器会使用这些约束来解析要调用的重载函数或初始化程序。

但如果我评论:

func getSomething<T, U>(_ function: @escaping (T) -> U) throws -> U

一切都开始工作了。这与编译器无法识别要解析的函数签名有关。

对此有什么特别的解决方案吗?

【问题讨论】:

    标签: ios swift generics closures overloading


    【解决方案1】:

    您还没有完全关注实际问题。让我们消除与示例无关的所有内容。这可以按预期编译和工作:

    struct TestFinder {
    
        func doSomething<T,U>(_ function: (T,U) -> Void) -> Void {
            print("two")
        }
    
        func doSomething<T,U,V>(_ function: (T,U,V) -> Void) -> Void {
            print("three")
        }
    
        func doSomething<T,U,V,W>(_ function: (T,U,V,W) -> Void) -> Void {
            print("four")
        }
    
    }
    

    我们将在这里对其进行测试:

        func f(_ s1: String, _ s2: String, _ s3: String, _ s4: String) -> Void {}
        TestFinder().doSomething(f) // "four"
    

    但是如果你添加带有一个传递函数参数的版本,一切都会崩溃:

    struct TestFinder {
    
        func doSomething<T>(_ function: (T) -> Void) -> Void {
            print("one")
        }
    
        func doSomething<T,U>(_ function: (T,U) -> Void) -> Void {
            print("two")
        }
    
        func doSomething<T,U,V>(_ function: (T,U,V) -> Void) -> Void {
            print("three")
        }
    
        func doSomething<T,U,V,W>(_ function: (T,U,V,W) -> Void) -> Void {
            print("four")
        }
    }
    

    现在我们无法编译,因为 first 版本被视为候选版本。事实上,如果我们删除 other 版本,我们仍然编译!

    struct TestFinder {
    
        func doSomething<T>(_ function: (T) -> Void) -> Void {
            print("one")
        }
    
    }
    

    这是奇怪的部分。我们仍然编译,即使我们说:

        func f(_ s1: String, _ s2: String, _ s3: String, _ s4: String) -> Void {}
        TestFinder().doSomething(f)
    

    显然,这个有四个参数的函数被编译器看作是“拟合”了只有一个泛型参数的声明。

    我认为这是一个错误。我想我可以猜到可能导致它的原因;它可能与作为元组的函数参数列表的遗留问题有关。这个函数f 是“等价于”一个函数,该函数采用由一个四字符串元组组成的 single 参数。尽管如此,您实际上不能调用 function 内的doSomething 与一个四字符串元组;我根本找不到调用它的方法。

    所以,我会说,将此视为一个错误,并通过删除您的泛型的 first 版本来暂时解决它。


    更新:根据 Swift 团队的建议,我使用 2020 年 5 月 4 日的 Swift 5.3 开发工具链进行了测试。有了它,您的代码将按预期进行编译和运行。这确实是一个错误,并且已作为

    的一部分进行了修复

    https://bugs.swift.org/browse/SR-8563

    回到我的版本,我的代码也按预期编译和运行,所有四个版本的doSomething 都存在。但是,请注意,如果您删除除第一个版本之外的所有 doSomething,它仍然可以编译并运行。此外,您可以使用四个参数调用function,方法是将它们捆绑到一个元组中并强制转换,如下所示:

    struct TestFinder2 {
    
        func doSomething<T>(_ function: (T) -> Void) -> Void {
            print("one")
            function(("manny", "moe", "jack", "henry") as! T)
        }
    
    }
    

    这似乎证实了我的猜测,即您所看到的是函数参数列表的隐藏元组性质的结果。从对 bug 的讨论中可以得出相同的结论,即“tuple-splatting”。

    【讨论】:

    • 我已经向 Apple 的 Swift 人员报告了这件事;让我们看看他们对此有什么看法。
    • 好吧,他们说它一个错误,并在即将发布的版本中修复。
    • 感谢您简化示例和问题。你在 swift 论坛上报告过吗?如果可以,可以分享一下链接吗?
    • 等一下,我要使用最新的工具链进行测试。
    • 好的,所以你的代码应该在最新的 Swift 5.3 开发工具链中编译,它应该可以像你期望的那样工作。我会更新我的答案以反映这一点。
    【解决方案2】:

    关键是 UserDetails 结构体,因为这个结构体有两个属性并且没有任何设计的初始化器,初始化器可以是 UserDetails(name: , roll: ) 或 UserDetails(name: ) 或 UserDetails (滚:),这是雄心勃勃的部分。如果你只是删除 UserDetails 的一个属性,它也可以工作,因为一个属性结构只有一个设计的初始值设定项。

    如果你评论

    func getSomething<T, U>(_ function: @escaping (T) -> U) throws -> U
    

    这意味着发现者只选择了一个:

    func getSomething<T,U,V>(_ function: @escaping(T,U) -> V) throws -> V
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多