对于下面的代码片段,为什么res1res2有不同的值?

func test() {
    let a: String? = nil
    let b: String? = nil

    let foo: (String?) -> Int = { $0 == nil ? 0 : 1}

    let res1 = [a, b].compactMap { $0 }.map(foo)
    let res2 = [a, b].compactMap { $0 }.map { foo($0) }

    print("res1: ", res1)
    print("res2: ", res2)
}

输出是

res1:  [0, 0]
res2:  []

推荐答案

这似乎是编译器基于下游操作 Select [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()
// a1 -> Array<Optional<Optional<String>>>
// cm1 -> Array<Optional<String>>
// a2 -> Array<Optional<String>>
// cm2 -> Array<String>
// res1:  [0, 0]
// res2:  []

看来:

  1. In the case of res1:
    • 因为foo接受String,所以map(foo)被输入,这样String?就被通过了——而map要接收String?compactMap必须返回[String?]
    • 为了让compactMap返回[String?],它的输入必须是[String??]
    • 虽然[a, b]默认为[String?],但编译器也可以隐式地将其upcast转换为[String??],所以它就是这么做的
    • 因此,仅从[String??]中移除一层可选性,并且String?个值中的每一个被传递到map并进入foo
  2. In the case of res2, the compiler isn't restricted as heavily by map { foo($0) }, since $0 can be implicitly upcast inside of the closure before being passed to foo, and this is what the compiler prefers:
    • $0是一个String,在传递到foo之前是upcastString?
    • map要接收String个值,compactMap必须返回[String]
    • 因为compactMap返回[String],所以它的输入必须是[String?],也就是[a, b],不需要向上投射
    • 因此,现在compactMap实际上是将[String?]中的nil过滤成[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) }

// a1 -> Array<Optional<String>>
// cm1 -> Array<Optional<String>>
// a2 -> Array<Optional<String>>
// cm2 -> Array<Optional<String>>
// res1:  [0, 0]
// res2:  [0, 0]

在本例中,编译器实际上在两个实例中都更喜欢surprising,因为return $0允许编译器从String?向上转换➡ String??(然后从[String??]向下过滤➡ [String?])!

无论如何,这似乎值得一份关于https://bugs.swift.org的报告,因为这种行为令人难以置信地惊讶.如果您继续并归档,我将很高兴向报告中添加测试用例和注释.

Swift相关问答推荐

启用完成并发判断以及如何解决警告(续)

@ MainActor + Combine不一致的编译器错误

对多个项目的数组进行排序

如何分解阿拉伯字母?

SwiftUI map 旋转

格式化单位和度量值.为什么从符号初始化的单位不能产生与静态初始化相同的结果

如何把多个可观测性变成一个完备性?

从SwiftUI使用UIPageView时,同一页面连续显示两次的问题

如何在SwiftUI中做出适当的曲线?

KMM-Swift铸造密封类

expose 不透明的底层类型

Swift计时器未触发

为什么我的Swift app不能用xcodebuild构建,但是用XCode可以构建?

Swift // Sprite Kit:类别位掩码限制

从 iPhone 中的安全飞地获取真正的随机数?

为 SwiftUI 中的属性提供默认值

不要从 NumberFormatter 获取货币符号

在 Swift 中强制崩溃的最简单方法

即使设置为从不也可以访问 iOS11 照片库

swift中静态函数和单例类的区别