【问题标题】:Map and flatMap difference in optional unwrapping in Swift 1.2Swift 1.2 中可选展开的 Map 和 flatMap 差异
【发布时间】:2015-04-10 08:23:59
【问题描述】:

mapflatMap 都在 ImplicitlyUnwrappedOptional 上定义,但根据文档,它们的定义(显然)不同:

func map(f: @noescape (T) -> U) -> U!

如果 self == nil,则返回 nil。否则,返回 f(self!)。

func flatMap(f: @noescape (T) -> U!) -> U!

返回 f(self)!如果 self 和 f(self) 不为零。

我试着用一个简单的例子来使用它们:

let number: Int? = 1

let res1 = number.map { $0 + 1 }.map { $0 + 1 }
let res2 = number.flatMap { $0 + 1 }.flatMap { $0 + 1 }

res1 //3
res2 //3

但即使numbernil.,它们也会产生相同的结果 所以我的问题是,如果我将mapflatMap 应用于ImplicitlyUnwrappedOptionals,它们之间的实际区别是什么?我应该在什么时候选择哪一个?

【问题讨论】:

    标签: swift dictionary bind optional flatmap


    【解决方案1】:

    (备注:答案已更新,以反映 Swift 3 及更高版本中的语法变化,例如废除了ImplicitlyUnwrappedOptional。)

    Optional.map()Optional.flatMap() 声明如下(我省略了与此处无关的 throws/rethrows 修饰符):

    func map<U>(_ transform: (Wrapped) -> U) -> U?
    func flatMap<U>(_ transform: (Wrapped) -> U?) -> U?
    

    让我们考虑使用“map”的第一个示例的简化版本:

    let number: Int? = 1
    let res1 = number.map { $0 + 1 }
    print(res1) // Optional(2)
    

    number 的类型为Int?,闭包类型推断为(Int) -&gt; IntUInt,返回值的类型为Int?number 不是nil,所以它被解包并通过1 被传递给闭包。闭包返回2map 返回Optional(2)。如果numbernil,那么结果将是nil

    现在我们考虑使用“flatMap”的第二个示例的简化版本:

    let number: Int? = 1
    let res2 = number.flatMap { $0 + 1 }
    print(res2) // Optional(2)
    

    flatMap 需要 (Wrapped) -&gt; U? 类型的闭包,但 { $0 + 1 } 不返回可选值。为了使其编译,编译器将其转换为

    let res2 = number.flatMap { return Optional($0 + 1) }
    

    现在闭包的类型为(Int) -&gt; Int?,而U 又是Int。同样,number 被解包并传递给闭包。闭包返回Optional(2),这也是flatMap的返回值。如果numbernil ,如果闭包将返回nil,那么结果将是nil

    所以这些调用之间确实没有区别:

    let res1 = number.map { $0 + 1 }
    let res2 = number.flatMap { $0 + 1 }
    

    但这不是flatMap 的用途。一个更现实的例子是

    func foo(_ s : String?) -> Int? {
        return s.flatMap { Int($0) }
    }
    
    print(foo("1")) // Optional(1)
    print(foo("x")) // nil (because `Int($0)` returns nil)
    print(foo(nil)) // nil (because the argument is nil)
    

    通常,map 采用 (Wrapped) -&gt; U 类型的闭包并进行转换

    Optional<Wrapped>.none          --> Optional<U>.none
    Optional<Wrapped>.some(wrapped) --> Optional<U>.some(transform(wrapped))
    

    flatMap 采用 (Wrapped) -&gt; U? 类型的闭包并转换

    Optional<Wrapped>.none          --> Optional<U>.none
    Optional<Wrapped>.some(wrapped) --> transform(wrapped)
    

    这里的transform(wrapped) 也可以是Optional&lt;U&gt;.none

    如果(如您的示例中)flatMap 被调用时使用了一个 not 返回可选项的闭包,则编译器会自动将其转换为可选项,并且与 map 没有区别没有了。

    【讨论】:

    • 确实,mapflatMap 之间的区别很难辨别,因为它们对所采用的闭包类型并不严格。在许多情况下,它们可以采用相同的闭包并产生相同的结果。如果闭包不产生 Optional,也许 flatMap 应该拒绝。
    • Optional.map() 可以返回nil,因为U 是一个泛型类型,可以是任何你想要的类型,包括可选类型。
    • @PeterSchorn:是的,只要涉及嵌套选项nil 可能意味着不同的东西。为了避免这种歧义,我试图重写答案。当然U也可以是可选类型。
    【解决方案2】:

    这对于 map() 是不可能的,其中映射闭包具有签名 (T) -&gt; U

    这不太对。在我看来,Martin R 的回答并没有完全解决问题的核心,即文档没有正确描述 mapflatMap 之间的区别。

    区别在于不是他们采取什么样的封闭方式。每个人都会很高兴地接受一个产生 nonOptional 的闭包或一个产生 Optional 的闭包——不管文档怎么说,尽管他们的声明有所不同。所有这些表达式都编译:

    let i : Int? = nil
    let result1 = i.map {_ in "hello"} // map, closure produces nonOptional
    let result2 = i.flatMap {_ in "hello"} // flatMap, closure produces nonOptional
    let result3 = i.map {_ in Optional("hello") } // map, closure produces Optional
    let result4 = i.flatMap {_ in Optional("hello") } // flatMap, closure produces Optional
    


    好的,那么实际的区别是什么?这是flatMap 所做的,以防闭包确实产生一个 Optional:它解开它,从而防止双重包装的 Optional:

    let i : Int? = nil
    let result1 = i.map {_ in "hello"} // String?
    let result2 = i.flatMap {_ in "hello"} // String?
    let result3 = i.map {_ in Optional("hello") } // String?? // double-wrapped
    let result4 = i.flatMap {_ in Optional("hello") } // String? // not double-wrapped
    

    这是地图和平面地图之间的唯一区别。

    【讨论】:

    • 我对它的解释有点不同。 flatMap 采用(T) -&gt; U? 类型的闭包,即闭包总是返回一个可选的。在第二个示例中,闭包{_ in "hello"}被编译器转换为{_ in Optional("hello") },因此与第四个示例相同。在这两种情况下,Optional("hello") 都会返回,没有展开。
    • 是的,flatMap 实际上并没有解开闭包返回的可选项;不同之处在于它不会将闭包的结果包装在另一个可选中,而map 会。这是您的答案未解决的方法之间的根本区别。
    【解决方案3】:

    flatMap 解析嵌套选项,而 map 不解析。

    平面图

    var temp: Int? = 3
    var flag: Bool = false
    print(temp.flatMap { $0 < 5 ? 1 : nil } ?? .zero) 
    
    // output: 1
    

    地图

    var temp: Int? = 3
    var flag: Bool = false
    print(temp.map { $0 < 5 ? 1 : nil } ?? .zero) 
    
    // output: Optional(Optional(1))
    

    【讨论】:

      【解决方案4】:

      [map vs compactMap vs flatMap]

      Swift 可选地图 vs flatMap

      让我们创建自己的 Optional 类型的简单实现并实现 mapflatMap 函数

      enum CustomOptional<T> {
          case none
          case some(T)
          
          public init(_ some: T) {
              self = .some(some)
          }
          
          func map<U>(_ transform: (T) -> U) -> CustomOptional<U> {
              switch self {
              case .some(let value):
                  let transformResult: U = transform(value)
                  let result: CustomOptional<U> = CustomOptional<U>(transformResult) //<-- force wrap the transformResult
                  return result
              case .none:
                  return .none
              }
          }
          
          func flatMap<U>(_ transform: (T) -> CustomOptional<U>) -> CustomOptional<U> {
              switch self {
              case .some(let value):
                  let transformResult: CustomOptional<U> = transform(value)
                  let result: CustomOptional<U> = transformResult
                  return result
              case .none:
                  return .none
              }
          }
      }
      
      • map - 可以返回 Optional Optional
      • flatMap - 可以扁平化 Optional Optional 到 Optional
      Optional.map { () -> T } -> Optional<T>
      Optional.map { () -> Optional<T> } -> Optional<Optional<T>>
      
      Optional.flatMap { () -> Optional<T> } -> Optional<T>
      
      

      maptransformed 函数的返回值被包装到map 函数返回值的Optional

      flatMaptransformed函数的返回值与flatMap函数的返回值相同

      //problem
      
      //T == Int, U == CustomOptional<String>
      //map<U>(_ transform: (T) -> U) -> CustomOptional<U>
      //map<CustomOptional<String>>(_ transform: (Int) -> CustomOptional<String>) -> CustomOptional<CustomOptional<String>>
      let result: CustomOptional<CustomOptional<String>> = CustomOptional(1).map { int in
          return CustomOptional("Hello: \(int)")
      }
      
      //solution
      
      //T == Int, U == String
      //flatMap<U>(_ transform: (T) -> CustomOptional<U>) -> CustomOptional<U>
      //flatMap<U>(_ transform: (Int) -> CustomOptional<String>) -> CustomOptional<String>
      let result5: CustomOptional<String> = CustomOptional(1).flatMap { int in
          return CustomOptional("Hello: \(int)")
      }
      

      [Swift Functor, Applicative, Monad]

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多