在flatMap中"无法转换返回表达式",其中包含无意义的表达式

Pav*_*nov 6 arrays flatmap swift

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

例子

 
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)
}

评论一下

 
array
    .flatMap {
//        print("DD")
        return $0
    }
    .forEach {
        print("SS")
        print($0)
}

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

 
array
    .flatMap {
        let z = $0
        return $0  // Or return z - all is the same "Cannot convert return expression of type 'Int' to return type 'String?'"
    }
    .forEach {
        print("SS")
        print($0)
}

什么可能导致这种行为?

Ham*_*ish 5

flatMap(_:)上方法Sequence目前(如夫特4)具有两种不同的含义:

  • 它可以接受一个返回一个 optional 的转换闭包,T?它将返回一个[T],过滤掉nil结果(这个重载将在未来的版本中重命名compactMap(_:))。

    public func flatMap<ElementOfResult>(
      _ transform: (Element) throws -> ElementOfResult?
    ) rethrows -> [ElementOfResult]
    Run Code Online (Sandbox Code Playgroud)

  • 它可以接受一个返回 a 的转换闭包,Sequence它将返回一个包含所有结果序列串联的数组。

    public func flatMap<SegmentOfResult : Sequence>(
      _ transform: (Element) throws -> SegmentOfResult
    ) rethrows -> [SegmentOfResult.Element]
    Run Code Online (Sandbox Code Playgroud)

现在,在 Swift 4 中,String变成了 a RangeReplaceableCollection(因此是 a 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 }
Run Code Online (Sandbox Code Playgroud)

现在这样做:

// 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 } 
Run Code Online (Sandbox Code Playgroud)

为了保持源代码兼容性,为字符串添加了专门的flatMap重载:

//===----------------------------------------------------------------------===//
// 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)
  }
}
Run Code Online (Sandbox Code Playgroud)

这样上面的用法仍然会[String]在 Swift 3 兼容模式中返回 a ,但[Character]在 Swift 4 中返回 a 。

那么,为什么

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)
    }
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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