美文网首页
flatMap 和双重 Optional

flatMap 和双重 Optional

作者: Josscii | 来源:发表于2016-05-30 18:05 被阅读706次

    上次刷微博的时候看到@唐巧大大发微博贴的几段代码,经过我整理成下面几种情况:

    let arr: [Int?] = [1,3,nil,4]
    
    let arr1: [Int?] = arr.flatMap { $0 } // [{some 1}, {some 3}, nil, {some 4}]
    
    let arr2: [Int?] = arr.flatMap {
      next -> Int?? in
      next
    } // [{some 1}, {some 3}, nil, {some 4}]
    
    let arr3: [Int?] = arr.flatMap { 
        next -> Int? in
        next
    } // [{some 1}, {some 3}, {some 4}]
    
    let arr4 = arr.flatMap { $0 } // [1,3,4]
    
    let arr5: [Int] = arr.flatMap { $0 } // [1,3,4]
    

    我不理解为什么这几段代码会让 flatMap 有不同的行为,断断续续想了几天还没没有结果,于是决定去 StackOverFlow 上提这个问题,问题在这里,很幸运的是很快就有人回答了这个问题,并且回答得还不错,我把别人的答案和自己的理解记录下来。

    相信很多人对 Array 的 flatMap command + click 进去看 flatMap 的声明的时候,都会发现 SequenceType 下面有两个,

    public func flatMap<S : SequenceType>(transform: (Self.Generator.Element) throws -> S) rethrows -> [S.Generator.Element]
    
    public func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]
    

    第一个是用来把数组拍平的,第二个是用来把 nil 值去掉的,这里对这个问题有部分的解释,但还不够详细,于是我就去 Swift 的源代码中查找,终于找到了 flatMap 的实现,这里贴出部分的代码:

    public func flatMap<SegmentOfResult : Sequence>(
        _ transform: (Elements.Iterator.Element) -> SegmentOfResult
      ) -> LazySequence<
        FlattenSequence<LazyMapSequence<Elements, SegmentOfResult>>> {
        return self.map(transform).flatten()
     }
      
    public func flatMap<ElementOfResult>(
        _ transform: (Elements.Iterator.Element) -> ElementOfResult?
      ) -> LazyMapSequence<
        LazyFilterSequence<
          LazyMapSequence<Elements, ElementOfResult?>>,
        ElementOfResult
      > {
        return self.map(transform).filter { $0 != nil }.map { $0! }
    }
    

    第一个 flatMap,先对自己做 map 操作,然后对自己做 flatten 操作。

    第二个 flatMap 比较有意思,先对自己做 map 操作,然后过滤掉 nil 值,最后将 optional 转化为一般值。

    显然我们这里要讨论的是第二种 flatMap。

    当我们不对 flatMap 的返回值做任何的限制的时候,对应上面的代码中的 arr4,此时 T 被推断为 Int,所以 tansform 闭包简化后的类型为 Int? -> Int?,注意这个闭包是被传给 map 用的。

    看一下 Array 的 map 的声明:

    public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
    

    想想 map 的原理,很简单,其实就是遍历整个数组,对每个元素执行 transform 函数,然后返回一个包含这些元素的新数组。对于我们传入的 transform 闭包,这儿的 map 等于什么都没做,所以此时用于下一个 filter 操作的数组还是 [Int?],接下来就顺理成章了,剔除掉 nil 值,最后映射为非 optional 值。

    对 arr5 来说,情况一样。

    当我们指明 flatMap 的返回值为 [Int?] 时,情况就有所不同了。此时 T 被推断为 Int?,transform 闭包简化后的类型变为 Int? -> Int?? ,arr1 和 arr2 显然是等价的,我们先来分析一下,首先对遍历数组对数组的元素做 transform 操作,因为 transform 闭包返回的类型为 Int??,所以此时返回的新数组的元素的类型为 Int?? 或者更清楚一点为 Optional<Optional<Int>> ,接下来对数组剔除 nil 值,因为此时数组内并没有 nil(之前的 nil 被 map 为 Optional<nil>),所以 filter 并没起作用,最后 map 操作将元素强制解包为 Optional<Int>

    到这里,事情还没结束,我最不能理解的是 arr3 这种情况,和 arr1 一样,返回值都被强制的指明为 [Int?],但是发生变化的是传入的 transform 闭包,本来应该要接收的类型为 Int? -> Int??,但是传入的却为 Int? -> Int? ,这为什么可以呢?

    在我之前提到的 StackOverFlow 的回答中,这位热心的 @originaluser2 给我了这样的回答:

    Because you explicitly annotate the return type of the closure to be Int?, the closure will get implicitly promoted from (Element) -> Int? to (Element) -> Int?? (closure return types can get freely promoted in the same way as other types) – rather than the element itself being promoted from Int? to Int??, as without the type annotation the closure would be inferred to be (Element) -> Int??

    翻译下来就是说,Int? -> Int? 可以提升为 Int? -> Int??,或者说对于一个接受 Int? -> Int?? 的函数同样可以接受 Int? -> Int?

    并且他举了下面这个例子,

    func optionalIntArrayWithElement(closure: () -> Int??) -> [Int?] {
        let c = closure() // of type Int??
        if let c = c { // of type Int?
            return [c]
        } else {
            return []
        }
    }
    
    // another quirk: if you don't explicitly define the type of the optional (i.e write 'nil'),
    // then nil won't get double wrapped in either circumstance
    let elementA : () -> Int? = {Optional<Int>.None} // () -> Int?
    let elementB : () -> Int?? = {Optional<Int>.None} // () -> Int??
    
    // (1) nil gets picked up by the if let, as the closure gets implicitly upcast from () -> Int? to () -> Int??
    let arr = optionalIntArrayWithElement(elementA)
    
    // (2) nil doesn't get picked up by the if let as the element itself gets promoted to a double wrapped optional
    let arr2 = optionalIntArrayWithElement(elementB)
    
    if arr.isEmpty {
        print("nil was filtered out of arr") // this prints
    }
    
    if arr2.isEmpty {
        print("nil was filtered out of arr2") // this doesn't print
    }
    

    我们可以看到 optionalIntArrayWithElement 需要的是一个 () -> Int?? 的闭包,而 elementA 是一个 () -> Int? ,但是传进去完全没有问题。

    接着看代码的一段注释:

    another quirk: if you don't explicitly define the type of the optional (i.e write 'nil'), then nil won't get double wrapped in either circumstance

    也就是说如果只单纯的把 nil 赋值给 Int?? 的变量,那么这个 nil 不会被双重 optionla 包裹,对于双重 optional 可以看看喵神这篇

    这时候最难的部分来了,下面是我自己的理解,不知道是否完全正确,供大家讨论。

    从一开始说起,首先我们声明 arr 为 [Int?],对于里面的 nil 值,此时和 optional<Int>.None 等价,当我们 map arr 时,也就是对里面的元素进行 transform 操作时,虽然此时的 transform 函数被提升为返回 Int?? ,但是由于元素的 nil 值为普通的 nil,所以此时 nil 并没有变化为 Optional<Optional<Int>>.None,而普通的非 nil 当然可以变化为 Optional<Optional<Int>>.Some(3)等。所以最后 nil 值被去掉了,但是其他的非 nil 值仍旧没变。

    但是这里还有新的一个问题,就是当 transform 为默认返回为 Int?? 类型时,nil 值很显然被映射为了双重 nil,这是为啥呢?难道是默认的行为吗?期待有人能够分析解答。

    相关文章

      网友评论

          本文标题:flatMap 和双重 Optional

          本文链接:https://www.haomeiwen.com/subject/uohcdttx.html