【问题标题】:"Cannot convert return expression" in flatMap with a meaningless expression insideflatMap 中的“无法转换返回表达式”,其中包含无意义的表达式
【发布时间】:2018-06-06 16:35:52
【问题描述】:

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

示例

让数组 = [1, 2, 3, 4, 5, 6] 大批 .flatMap { 打印(“DD”) return $0 // 无法将“Int”类型的返回表达式转换为“String”类型的返回值? } .forEach { 打印(“SS”) 打印($0) }

稍微注释一下

大批 .flatMap { // 打印(“DD”) 返回 $0 } .forEach { 打印(“SS”) 打印($0) }

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

大批 .flatMap { 让 z = $0 return $0 // 或者 return z - 都是一样的“不能将'Int'类型的返回表达式转换为'String'类型的返回表达式?” } .forEach { 打印(“SS”) 打印($0) }

什么可能导致这种行为?

【问题讨论】:

    标签: arrays swift flatmap


    【解决方案1】:

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


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

    • 扁平化嵌套数组

      let array = [[1, 2, 3, 4, 5, 6], [7, 8, 9]]
      let flattened = array.flatMap{$0}
      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($0) }
      print(flattened) // [1, 2, 3, 4, 5, 6]
      

    【讨论】:

    • 是的,我没有考虑指定这一点,在我添加 (num) -> Int? in 之后,一切都按预期工作。谢谢你:)
    【解决方案2】:

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

    • 它可以接受一个转换闭包,返回一个可选的T?,它会返回一个[T],过滤掉nil的结果(这个重载is to be renamedcompactMap(_:)在未来的版本中)。

      public func flatMap<ElementOfResult>(
        _ transform: (Element) throws -> ElementOfResult?
      ) rethrows -> [ElementOfResult]
    • 它可以接受一个返回Sequence 的转换闭包,它会返回一个包含所有结果序列连接的数组。

      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 { $0 }
    

    现在这样做:

    // 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 { $0 } 
    

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

    //===----------------------------------------------------------------------===//
    // The following overloads of flatMap are carefully crafted to allow the code
    // like the following:
    //   ["hello"].flatMap { $0 }
    // 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 兼容模式下仍会返回 [String],但在 Swift 4 中返回 [Character]

    那么,为什么

    let array = [1, 2, 3, 4, 5, 6]
    
    array
        .flatMap {
            print("DD")
            return $0 // Cannot convert return expression of type 'Int' to return type 'String?'
        }
        .forEach {
            print("SS")
            print($0)
        }
    

    告诉你闭包应该返回一个String?

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

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

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

    array
      .flatMap { i -> Int? in
        print("DD")
        return i
      }
      .forEach {
        print("SS")
        print($0)
      }
    

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

    【讨论】:

    • 很好的答案,谢谢!我在实际代码中使用了flatMap(_:) 的可选过滤功能。我刚刚剥离了一些代码来显示问题,而与问题部分没有密切关系..
    猜你喜欢
    • 2016-08-03
    • 1970-01-01
    • 2018-07-31
    • 2021-01-18
    • 1970-01-01
    • 2021-08-01
    • 2023-03-12
    • 2018-09-29
    相关资源
    最近更新 更多