这两种表示 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
我不明白的是为什么他们 不会以同样的方式窒息。
看起来接受 Substring
的 Int.init
重载具有以下签名:
public init?<S>(_ text: S, radix: Int = 10) where S : StringProtocol
因此,Int([=15=])
可以工作,因为它使用默认值 radix
,但是没有接受 Substring
的 Int.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 { }
今天在看另外一个 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
我不明白的是为什么他们 不会以同样的方式窒息。
看起来接受 Substring
的 Int.init
重载具有以下签名:
public init?<S>(_ text: S, radix: Int = 10) where S : StringProtocol
因此,Int([=15=])
可以工作,因为它使用默认值 radix
,但是没有接受 Substring
的 Int.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 { }