首页 > 解决方案 > 从通用上下文快速调用非通用函数

问题描述

我在理解泛型函数中的匹配类型如何快速工作时遇到问题。我不明白为什么我的代码没有编译。

这就是我所拥有的:

enum LoadingState<T> {
    case idle
    case loading
    case failed(Error)
    case loaded(T)
}

private func updateInternal(_ state: Int) {
    print("=int")
}

private func updateInternal(_ state: String) {
    print("=string")
}

private func update<T>(_ state: LoadingState<T>) {
    switch state {
    case .loaded(let viewModel):
        updateInternal(viewModel)
    default:
        break
    }
}

let stateInt: LoadingState<Int> = .loaded(42)
let stateString: LoadingState<String> = .loaded(String("Hello"))

update(stateInt)
update(stateString)

我收到以下编译错误:

error: no exact matches in call to global function 'updateInternal'

但是,如果我添加通用函数,updateInternal我就可以编译代码:

private func updateInternal<T>(_ state: T) {
    print("=generic")
}

但是在运行代码时,只有这个通用函数在所有调用场景中都匹配。我能够通过以下方式解决此问题:

private func updateInternal<T>(_ state: T, type: T.Type) {
    if type == Int.self {
        updateInternal(state as! Int)
    }
    else if type == String.self {
        updateInternal(state as! String)
    }
}

private func update<T>(_ state: LoadingState<T>) {
    switch state {
    case .loaded(let viewModel):
        updateInternal(viewModel, type: T.self)
    default:
        break
    }
}

但我很确定这不应该是必要的,对吧?因此,我的问题是如何通过从通用上下文中调用非通用函数作为专用函数来解决这个问题

标签: swiftgenericspattern-matching

解决方案


您的代码看起来非常像您习惯于 C++ 模板编程(并且查看您的个人资料似乎证实了我的假设)。但是 Swift 泛型不是模板!

在这个函数中

private func update<T>(_ state: LoadingState<T>) {
    switch state {
    case .loaded(let viewModel):
        updateInternal(viewModel)
    default:
        break
    }
}

与 C++ 模板不同,编译器将只编译(或至少类型检查)该函数一次。

它将检查是否可以updateInternal使用泛型类型的值进行调用T

它怎么能检查呢?它将查看的约束T并尝试减少T到​​您的函数之外也可用的最具体的类型。由于您没有限制T为任何类型,编译器只知道 的所有值T都是协变的(兼容于)Any。但是你没有提供任何超载的updateInternalaccept Any

如果您提供

private func updateInternal<T>(_ state: T) {
    print("=generic")
}

当然,这可以称为,因为它实际上接受任何类型(或者,换一种说法,接受Any)。

如果您提供

private func updateInternal(_ state: Any) {
    print("=any")
}

相反,您的代码也会编译。

但是在运行代码时,只有这个通用函数在所有调用场景中都匹配

对,是真的。但是 Swift 的语义不允许编译器证明或使用这个事实。(与 C++ 不同,每个模板实例化都会导致编译器使用提供的类型单独编译模板。)

因此,我的问题是如何通过从通用上下文中调用非通用函数作为专用函数来解决这个问题

Swift 没有专门的函数,因为编译器再次编译泛型函数一次。编译器无法选择正确的专业化。同样的事情在 Swift 中通过面向对象/动态调度(virtual在 C++ 中)实现。

您可以对可以作为泛型参数传递的类型提供约束。例如,这将起作用:

private func update<T: String>(_ state: LoadingState<T>) {
    switch state {
    case .loaded(let viewModel):
        updateInternal(viewModel)
    default:
        break
    }
}

因为现在编译器知道T它将与 String 协变,它可以调用updateInternal. 但这当然是没有意义的。

您通常会定义一个包含您需要的 T 的所有功能的协议(例如提供和 updateInternal 方法!)并将 T 约束到它。

我建议您了解有关类型约束和协议的更多信息。


推荐阅读