带有元素的数组上的Swift flatMap具有不同的行为

时间:2016-05-29 03:10:17

标签: swift optional type-inference flatmap

let arr: [Int?] = [1,2,3,4,nil]

let arr1 = arr.flatMap { next in
    next
}
// arr1: [1,2,3,4]
let arr2: [Int?] = arr.flatMap { next -> Int? in
   next
}
// arr2: [Optional(1), Optional(2), Optional(3), Optional(4)]

我对这些代码感到困惑,他们为什么会有所作为?

更新:请参阅这些代码,我

let arr: [Int?] = [1,2,3,4,nil]

let arr1: [Int?] = arr.flatMap { next in
    next
}
// arr1: [Optional(1), Optional(2), Optional(3), Optional(4), nil]
let arr2: [Int?] = arr.flatMap { next -> Int? in
    next
}
// arr2: [Optional(1), Optional(2), Optional(3), Optional(4)]

2 个答案:

答案 0 :(得分:4)

作为@Adam says,由于您为结果提供的显式类型。在第二个例子中,这会导致双重包装选项引起的混乱。为了更好地理解这个问题,我们来看看flatMap函数签名。

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

当您明确指定结果属于[Int?]类型时,因为flatMap返回通用类型[T] - Swift会将T推断为Int?

现在这会引起混淆,因为传递给flatMap的闭包需要一个元素输入并返回T?。因为TInt?,所以此闭包现在将返回T??(双包装可选)。这样可以很好地编译,因为类型可以自由地提升为选项,包括提升为双选项的选项。

所发生的事情是,数组中的Int?个元素被提升为Int??个元素,然后flatMap将它们展开回Int? }。这意味着nil元素无法从arr1过滤掉,因为它们会被双重包裹,而flatMap仅在第二层包装上运行。

为什么arr2 能够将nil过滤掉,似乎是因为关闭的促销传递给flatMap。因为您明确地将闭包的返回类型注释为Int?,所以闭包将从(Element) -> Int?隐式提升为(Element) -> Int??(闭包返回类型可以像其他方式一样自由地提升类型) - 而不是元素本身Int?升级到Int??,因为没有类型注释,闭包将被推断为(Element) -> Int??

这个怪癖似乎允许nil避免被双重包裹,因此允许flatMap过滤掉它(不完全确定这是否是预期的行为)。

您可以在以下示例中看到此行为:

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
}

故事的道德

避开双重包裹的选项,他们可以给你超级混乱的行为!

如果您正在使用flatMap,那么如果您通过[Int],则应该会期望返回[Int?]。如果您想保留元素的可选性,请改用map

答案 1 :(得分:1)

它与您提供的显式类型arr2有关。当您指定arr2必须是[Int?]类型时,Swift符合并简单地将值包装在可选项中。但是,flatMap操作返回数组的非零值,这使arr1[Int]类型的原因显而易见。要查看flatMap返回数组的非零值,只需查看函数的声明注释即可。此外,如果您了解flatMap操作通常使用的内容,即它解开值的内部包装,您可以看到函数返回类型[Int]的值的原因。考虑arr,其类型为[Int?]。在其包装形式中重写arr的类型,它变为Array<Optional<Int>>。对于要打开内包装的flatMap函数,函数必须返回类型Array<Int>的值。为此,函数必须抛出零值。