compactMap 在存储在可选变量中时表现不同

compactMap behaves differently when storing in an optional variable

考虑以下数组。

let marks = ["86", "45", "thiry six", "76"]

我在以下两个案例中解释了我的疑惑。

案例#1

// Compact map without optionals
let compactMapped: [Int] = marks.compactMap { Int([=11=]) }
print("\(compactMapped)")

结果 - [86, 45, 76]

案例#2

// Compact map with optionals
let compactMappedOptional: [Int?] = marks.compactMap { Int([=12=]) }
print("\(compactMappedOptional)")

结果 - [可选(86),可选(45),无,可选(76)]

为什么Case#2的结果是“nil”?谁能解释为什么案例#2 中的 [Optional(86), Optional(45), Optional(76)] 不是这样的? (PFA 游乐场)

我将此行为作为 bug at bugs.swift.org 提交,结果返回时“按预期工作”。为了找到一种向您解释的方法,我不得不对答复进行一些思考;我认为这非常准确和清楚地重新表达了它。我们开始了!

为了看看这里发生了什么,让我们自己写一些类似 compactMap 的东西。假设 compactMap 做了三件事:

  1. 通过给定的transform映射原始数组,期望产生Optionals;在此特定示例中,它生成 Int? 个元素。

  2. 过滤掉 nils。

  3. 强制解包 Optionals(安全,因为现在没有 nils)。

所以这是“正常”行为,分解为这种理解方式:

let marks = ["86", "45", "thiry six", "76"]
let result = marks.map { element -> Int? in
    return Int(element)
}.filter { element in
    return element != nil
}.map { element in
    return element!
}

好的,但是在你的例子中,转换为 [Int?] 告诉 compactMap 输出 Int?,这意味着它的第一个 map 必须产生 Int??.

let result3 = marks.map { element -> Int?? in
    return Int(element) // wrapped in extra Optional!
}.filter { element in
    return element != nil
}.map { element in
    return element!
}

所以第一个map产生双重包装的Optionals,即Optional(Optional(86))Optional(Optional(45))Optional(nil)Optional(Optional(76)).

None 个是 nil,所以它们都通过过滤器,然后它们都被展开一次以给出您要打印的结果。

回复我的报告的 Swift 专家承认这有一些违反直觉的地方,但这是我们为分配给 Optional 执行自动包装的自动行为付出的代价。换句话说,你可以说

let i : Int? = 1

因为 1 在你进入作业的过程中被包裹在一个 Optional 中。您的 [Int?] 演员要求完全相同的行为。

解决方法是自己明确指定转换的输出类型:

let result3 = marks.compactMap {element -> Int? in Int(element) }

这会阻止编译器自己得出关于 map 函数的输出类型应该是什么的结论。问题已解决。

[您可能还想查看 Swift 中关于类型推断的 WWDC 2020 video。]