这似乎是编译器根据下游操作选择[a, b].compactMap
类型的结果。您可以通过检查参数在函数中传递时的类型来了解此情况:
func printType<T>(_ i: String, _ v: T) -> T {
print(i, "->", T.self)
return v
}
func test() {
let a: String? = nil
let b: String? = nil
let foo: (String?) -> Int = { $0 == nil ? 0 : 1 }
let res1 = printType("cm1", printType("a1", [a, b]).compactMap { $0 }).map(foo)
let res2 = printType("cm2", printType("a2", [a, b]).compactMap { $0 }).map { foo($0) }
print("res1: ", res1)
print("res2: ", res2)
}
test()
看起来:
1. 对于 `res1`:
- 因为 `foo` 接受一个 `String`, 所以 `map(foo)` 的类型会是一个 `String?` 被传递出去 —— 为了让 `map` 接收到 `String?`,`compactMap` 必须返回一个 `[String?]`
- 为了让 `compactMap` 返回一个 `[String?]`,它的输入必须是一个 `[String??]`
- 尽管 `[a, b]` 默认情况下是一个 `[String?]`,编译器也可以将其隐式地
向上转型 成一个 `[String??]`,这就是它所做的
- 因此,仅从 `[String??]` 中删除了一层选项,每个 `String?` 值都被传递给了 `map` 和 `foo`
2. 对于 `res2`,编译器由于 `map { foo($0) }` 的存在而没有受到严格限制,因为 `$0` 可以在传递给 `foo` 之前在闭包中被隐式向上转型,并且这正是编译器所喜欢的:
- `$0` 是一个 `String`,在传递给 `foo` 之前被上转型为 `String?`
- 为了让 `map` 接收到 `String` 值,`compactMap` 必须返回一个 `[String]`
- 由于 `compactMap` 返回一个 `[String]`,它的输入必须是一个 `[String?]`,而 `[a, b]` 正是如此,无需上转型
- 因此,现在 `compactMap` 实际上将 `nil` 从 `[String?]` 中过滤出来并转换成 `[String]`,由于没有值留下,`foo` 没有被调用(可以通过 `foo` 内部进行更多打印语句来查看这一点)
这种情况在编译器中是非常具体的,并且你恰好找到了其中发生的一个特定情况。例如,编译器会以与多语句闭包不同的方式解析单语句闭包的结果:如果将 `compactMap { $0 }` 改为 `compactMap { _ = 1 + 1; return $0 }`,则闭包将被解析为不同的形式,并且类型检查将按照不同的顺序进行,导致两种情况都返回 `[]`。
let res1 = printType("cm1", printType("a1", [a, b]).compactMap { _ = 1 + 1; return $0 }).map(foo)
let res2 = printType("cm2", printType("a2", [a, b]).compactMap { _ = 1 + 1; return $0 }).map { foo($0) }
在这种情况下,编译器实际上在两个实例中都倾向于选择
令人惊奇的情况,因为
return $0
允许编译器从
String?
➡
String??
(然后从
[String??]
➡
[String?]
)进行向上转型! 无论如何,这似乎值得在
https://bugs.swift.org 上报告,因为这种行为非常令人惊讶。如果您继续提交报告,我将很高兴添加测试用例和注释到报告中。
compacted()
对于每个结果都产生了正确的结果。现在通常都使用compacted
。维护算法包的人最关心这个。 - user652038