"Cannot convert return expression" 在 flatMap 中,里面有一个无意义的表达式

"Cannot convert return expression" in flatMap with a meaningless expression inside

我正在检查 .lazy 的高阶函数,发现了一些与 flatMap 函数(可能还有其他函数)相关的有趣的编译错误

例子

让数组 = [1, 2, 3, 4, 5, 6]

大批
    .flatMap {
        打印("DD")
        return $0 // 无法将类型 'Int' 的 return 表达式转换为 return 类型 'String?'
    }
    .forEach {
        打印("SS")
        打印($0)
}

注释掉一点

大批
    .flatMap {
// 打印("DD")
        return $0
    }
    .forEach {
        打印("SS")
        打印($0)
}

一切正常..更有趣的例子

大批
    .flatMap {
        让 z = $0
        return $0 // 或者 return z - 都是一样的 "Cannot convert return expression of type 'Int' to return type 'String?'"
    }
    .forEach {
        打印("SS")
        打印($0)
}

什么会导致这种行为?

flatMap 根据上下文可以有不同的含义。您可能会更准确地告诉编译器您打算使用哪一个。


flatMap 应用于编译器可以推断的数组的两个通常目的是

  • 展平嵌套数组

    let array = [[1, 2, 3, 4, 5, 6], [7, 8, 9]]
    let flattened = array.flatMap{[=10=]}
    print(flattened) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
    
  • 映射到另一种类型并过滤选项

    let array = ["1", "a", "2", "3", "b", "4", "5", "6"]
    let flattened = array.flatMap{ Int([=11=]) }
    print(flattened) // [1, 2, 3, 4, 5, 6]
    

目前 Sequence 上的 flatMap(_:) 方法(从 Swift 4 开始)有两种不同的含义:

  • 它可以采用 return 一个可选 T? 的转换闭包,它会 return 一个 [T],过滤掉 nil 结果(在未来的版本中将 is to be renamed 重载到 compactMap(_:))。

    public func flatMap<ElementOfResult>(
      _ transform: (Element) throws -> ElementOfResult?
    ) rethrows -> [ElementOfResult]
    

  • 它可以采用一个 return 是一个 Sequence 的转换闭包,它将 return 一个包含所有结果序列串联的数组。

    public func flatMap<SegmentOfResult : Sequence>(
      _ transform: (Element) throws -> SegmentOfResult
    ) rethrows -> [SegmentOfResult.Element]
    

现在,在 Swift 4 中,String 变成了 RangeReplaceableCollection(因此变成了 Sequence)。所以 Swift 3 个代码执行此操作:

// returns ["foo"], as using the `nil` filtering flatMap, the elements in the closure
// are implicitly promoted to optional strings.
["foo"].flatMap { [=12=] }

现在这样做:

// returns ["f", "o", "o"], a [Character], as using the Sequence concatenation flatMap,
// as String is now a Sequence (compiler favours this overload as it avoids the implicit
// conversion from String to String?)
["foo"].flatMap { [=13=] } 

为了保持源兼容性,专门 flatMap 重载 were added 字符串:

//===----------------------------------------------------------------------===//
// The following overloads of flatMap are carefully crafted to allow the code
// like the following:
//   ["hello"].flatMap { [=14=] }
// return an array of strings without any type context in Swift 3 mode, at the
// same time allowing the following code snippet to compile:
//   [0, 1].flatMap { x in
//     if String(x) == "foo" { return "bar" } else { return nil }
//   }
// Note that the second overload is declared on a more specific protocol.
// See: test/stdlib/StringFlatMap.swift for tests.
extension Sequence {
  @_inlineable // FIXME(sil-serialize-all)
  @available(swift, obsoleted: 4)
  public func flatMap(
    _ transform: (Element) throws -> String
  ) rethrows -> [String] {
    return try map(transform)
  }
}

extension Collection {
  @_inlineable // FIXME(sil-serialize-all)
  public func flatMap(
    _ transform: (Element) throws -> String?
  ) rethrows -> [String] {
    return try _flatMap(transform)
  }
}

这样上面的用法在 Swift 3 兼容模式下仍然是 return [String],但在 Swift 4.

那么,为什么

let array = [1, 2, 3, 4, 5, 6]

array
    .flatMap {
        print("DD")
        return [=15=] // Cannot convert return expression of type 'Int' to return type 'String?'
    }
    .forEach {
        print("SS")
        print([=15=])
    }

告诉你闭包应该return一个String??

好吧,Swift 目前不会为多语句闭包推断参数和 return 类型(有关详细信息,请参阅 this Q&A)。因此 flatMap(_:) 重载,其中闭包 return 是泛型 T? 或泛型 S : Sequence 在没有显式类型注释的情况下不符合调用条件,因为它们需要类型推断以满足通用占位符。

因此,唯一符合条件的重载是特殊的 String 源兼容性重载,因此编译器期望闭包为 return a String?.

要解决此问题,您可以显式注释闭包的 return 类型:

array
  .flatMap { i -> Int? in
    print("DD")
    return i
  }
  .forEach {
    print("SS")
    print([=16=])
  }

但是,如果您实际上并未在实际代码中使用此 flatMap(_:) 重载的可选过滤功能,则应改用 map(_:)