这两种表示 map 函数的方法不是等价的吗?

What aren't these two ways of expressing map function equivalent?

今天在看另外一个 SO 问题时得到了一个惊喜:

let s = "1,a"
let arr = s.split(separator: ",")
let result = arr.compactMap{Int([=10=])} // ok
let result2 = arr.compactMap(Int.init) // error

为什么第 3 行合法而第 4 行不合法?我本以为这两种说法“如果可能的话将传入参数强制转换为 Int”是完全等价的。

我明白第4行在Subsequence上卡住了,我看看如何走出困境:

let result2 = arr.map(String.init).compactMap(Int.init) // ok

我不明白的是为什么他们 不会以同样的方式窒息。

看起来接受 SubstringInt.init 重载具有以下签名:

public init?<S>(_ text: S, radix: Int = 10) where S : StringProtocol

因此,Int([=15=]) 可以工作,因为它使用默认值 radix,但是没有接受 SubstringInt.init(_:) - 只有 Int.init(_:radix:) 确实如此 - 所以它失败了。

但如果有的话:

extension Int {
    public init?<S>(_ text: S) where S : StringProtocol {
        self.init(text, radix: 10)
    }
}

那么这会起作用:

let result1 = arr.compactMap(Int.init)

实际上第一个版本(Int([=14=]))调用了这个初始化器,它有两个参数(其中一个有默认值):

@inlinable public init?<S>(_ text: S, radix: Int = 10) where S : StringProtocol

如果我像这样定义自定义初始化程序,那么第二个示例也可以。

extension Int {
    init?<S>(_ string: S) where S: StringProtocol {
        // convert somehow, e.g: self.init(string, radix: 10)
        return nil
    }
}

let result2 = arr.compactMap(Int.init)

在我看来,如果我在compactMap中写Int.init,它只能调用确切的初始化器(或函数),而第一个的第二个参数无法推断调用的初始值设定项。

另一个例子:

func test1<S>(param1: S) -> String where S: StringProtocol {
    return ""
}

func test2<S>(param1: S, defaultParam: String = "") -> String where S: StringProtocol {
    return ""
}

extension Sequence {
    func customCompactMap<ElementOfResult>(_ transform: (Element) -> ElementOfResult?) -> [ElementOfResult] {
        compactMap(transform)
    }
}

arr.customCompactMap(test1)
arr.customCompactMap(test2) // error

我认为函数引用不能包含任何默认值。不幸的是,我没有找到任何官方参考资料,但看起来很有趣。

证明,最后一个例子:

func test3(param1: String, defaultParam: String = "") { }
let functionReference = test3
functionReference("", "")
functionReference("") // error

这里的functionReference's类型是(String, String) -> (),尽管test3函数的第二个参数有默认值。如您所见,functionReference 不能只用一个值调用。

我试着寻找 Swift 论坛 post 核心团队的某个人对此进行了解释,但抱歉,我找不到。你可以去那里问清楚这一点:

默认参数实际上不会产生重载。

相反,在调用点使用默认参数是使用所有参数的语法糖。编译器会为您不使用的那些插入默认值。

一些结果……


您不能将带有默认参数的函数用作带有简化签名的闭包。正如您在问题中所展示的那样,您必须将它们包装在新的闭包中。

func ƒ(_: Int = 0) { }

let intToVoid: (Int) -> Void = ƒ // compiles

// Cannot convert value of type '(Int) -> ()' to specified type '() -> Void'
let voidToVoid: () -> Void = ƒ

具有不同默认参数模式但在调用点看起来相同的方法不被视为覆盖。

class Base {
  func ƒ(_: Any? = nil) -> String { "Base" }
}

final class Derived: Base {
  // No `override` required.
  func ƒ() -> String { "Derived" }
}

Base().ƒ() // "Base"
Derived().ƒ() // "Derived"
(Derived().ƒ as (Any?) -> String)("argument") // "Base"

默认参数不允许满足协议要求。

protocol Protocol {
  func ƒ() -> String
}

// Type 'Base' does not conform to protocol 'Protocol'
extension Base: Protocol { }