您可以迅速将函数作为参数传递给接受闭包的函数。这对于避免在使用运算符时语法上污染代码非常有用。例如,您可以编写一个总和,如下所示:
let values = 0 ..< 10
let sum = values.reduce(0, +)
不幸的是,当Swift的推论无法从其他参数确定预期闭包的类型时,重载函数会导致模棱两可的情况。例如,考虑下面的代码。最后一行无法编译,因为Swift无法确定我要指的+
的“版本”。
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
guard let first = pair.0 as? T, let second = pair.1 as? T
else { return nil }
return fn(first, second)
}
// The following line cannot compile.
let x = castAndCombine((1, 2), with: +)
不幸的是,没有(或者至少我不知道)任何方法来指定我的意思是+
。尽管如此,我还是提出了两个解决方案:
func castAndCombine<T, U>(_ pair: (Any, Any), toType: T.Type, with fn: (T, T) -> U) -> U? {
// ...
}
let x = castAndCombine((1, 2), toType: Int.self, with: +)
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
// ...
}
let x = castAndCombine((1, 2), with: { (a: Int, b: Int) in a + b })
我个人不喜欢第一种解决方案,因为我觉得使用起来既不美观也不自然。但是,我不知道第二个是否会增加性能开销,这是由于创建了一个闭包,该闭包实际上包装了一个函数,而没有增加任何行为。
有人知道这种性能开销是否确实存在和/或在任何程度上都重要吗?
答案 0 :(得分:2)
如果您进行优化编译,则不会有任何开销,因为编译器很可能会内联您的闭包。
您可以通过比较Swift编写的LLVM代码,用第一个解决方案(因为它支持两种样式)来验证此假设。 LLVM是编译器在创建实际机器代码之前使用的一种中间表示形式。
直接使用运算符写一个文件,即:
let x = castAndCombine((1, 2), toType: Int.self, with: +)
使用闭包写入第二个文件,即:
let x = castAndCombine((1, 2), toType: Int.self, with: { (a: Int, b: Int) in a + b })
现在使用优化进行编译,要求Swift的编译器生成LLVM IR。假设您的文件名为main1.swift
和main2.swift
,则可以运行以下命令:
swift -O -emit-ir main1.swift 1>main1.ll
swift -O -emit-ir main2.swift 1>main2.ll
两个生成的文件应该相同。
diff main1.ll main2.ll
# No output
请注意,注释中建议的解决方案也不会增加任何性能开销,因为静态保证的强制转换不会花费任何操作。
答案 1 :(得分:1)
您可以将closure
强制转换为所需的类型,而不用创建+
来消除歧义:
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
guard let first = pair.0 as? T, let second = pair.1 as? T
else { return nil }
return fn(first, second)
}
// Add two Ints by concatenating them as Strings
func +(_ lhs: Int, _ rhs: Int) -> String {
return "\(lhs)\(rhs)"
}
if let x = castAndCombine((1, 2), with: (+) as (Int, Int) -> String) {
print(x)
}
12
if let x = castAndCombine((1, 2), with: (+) as (Int, Int) -> Int) {
print(x)
}
3