Swift 1.2 中可选展开的 Map 和 flatMap 差异
Map and flatMap difference in optional unwrapping in Swift 1.2
map
和 flatMap
都在 ImplicitlyUnwrappedOptional
上定义,但根据文档,它们的定义(显然)不同:
func map(f: @noescape (T) -> U) -> U!
If self == nil, returns nil. Otherwise, returns f(self!).
func flatMap(f: @noescape (T) -> U!) -> U!
Returns f(self)! iff self and f(self) are not nil.
我试着用一个简单的例子来使用它们:
let number: Int? = 1
let res1 = number.map { [=11=] + 1 }.map { [=11=] + 1 }
let res2 = number.flatMap { [=11=] + 1 }.flatMap { [=11=] + 1 }
res1 //3
res2 //3
但即使 number
是 nil.
,他们也产生了相同的结果
所以我的问题是,如果我将 map
或 flatMap
应用于 ImplicitlyUnwrappedOptional
,它们之间的实际区别是什么?我应该选择哪一个而不是另一个?
(备注:答案已更新,以反映Swift3及以后的语法变化,例如ImplicitlyUnwrappedOptional
的废除。)
Optional.map()
and 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 { [=11=] + 1 }
print(res1) // Optional(2)
number
的类型为 Int?
,闭包类型推断为 (Int) -> Int
。 U
是 Int
,return 值的类型是 Int?
。 number
不是 nil
,所以它被解包并传递 1
被传递给闭包。闭包 returns 2
和 map
returns Optional(2)
。如果 number
是 nil
那么结果将是 nil
.
现在我们考虑使用“flatMap”的第二个示例的简化版本:
let number: Int? = 1
let res2 = number.flatMap { [=12=] + 1 }
print(res2) // Optional(2)
flatMap
期望 (Wrapped) -> U?
类型的闭包,但 { [=38=] + 1 }
不是 return 可选的。为了使其编译,编译器将其转换为
let res2 = number.flatMap { return Optional([=13=] + 1) }
现在闭包的类型为 (Int) -> Int?
,U
又是 Int
。同样,number
被解包并传递给闭包。闭包 returns Optional(2)
也是来自 flatMap
的 return 值。如果 number
是 nil
或 如果闭包 return nil
那么结果将是 nil
.
所以这些调用之间确实没有区别:
let res1 = number.map { [=14=] + 1 }
let res2 = number.flatMap { [=14=] + 1 }
然而,这不是 flatMap
的目的。一个更现实的例子是
func foo(_ s : String?) -> Int? {
return s.flatMap { Int([=15=]) }
}
print(foo("1")) // Optional(1)
print(foo("x")) // nil (because `Int([=15=])` returns nil)
print(foo(nil)) // nil (because the argument is nil)
通常,map
采用类型 (Wrapped) -> U
的闭包并转换
Optional<Wrapped>.none --> Optional<U>.none
Optional<Wrapped>.some(wrapped) --> Optional<U>.some(transform(wrapped))
flatMap
采用类型 (Wrapped) -> U?
的闭包并转换
Optional<Wrapped>.none --> Optional<U>.none
Optional<Wrapped>.some(wrapped) --> transform(wrapped)
这里transform(wrapped)
也可以是Optional<U>.none
如果(如在您的示例中)调用 flatMap
时使用的闭包 not return 是可选的,那么编译器会将其转换为可选的自动,和 map
没有区别了。
This is not possible with map()
where the mapping closure has the signature (T) -> U
.
这不太正确。在我看来,Martin R 的回答并没有完全触及问题的核心,即文档没有正确描述 map
和 flatMap
之间的区别。
区别在于不是他们采取什么样的闭包。每个人都会愉快地接受一个产生非可选的闭包或一个产生可选的闭包——不管文档怎么说,尽管他们的声明有所不同。所有这些表达式编译:
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
这就是map和flatMap仅的区别。
flatMap
解析嵌套的可选值,而 map
不解析。
平面图
var temp: Int? = 3
var flag: Bool = false
print(temp.flatMap { [=10=] < 5 ? 1 : nil } ?? .zero)
// output: 1
地图
var temp: Int? = 3
var flag: Bool = false
print(temp.map { [=11=] < 5 ? 1 : nil } ?? .zero)
// output: Optional(Optional(1))
Swift 可选地图 vs flatMap
让我们创建自己的 Optional 类型的简单实现并实现 map
和 flatMap
函数
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
- 可以 return 可选 可选
flatMap
- 可以平 Optional Optional 到 Optional
Optional.map { () -> T } -> Optional<T>
Optional.map { () -> Optional<T> } -> Optional<Optional<T>>
Optional.flatMap { () -> Optional<T> } -> Optional<T>
map
:transformed
函数的 returned 值被封装到 map
函数的 returned 值的 Optional
中
flatMap
:returntransformed
函数的值与 returnflatMap
函数
的值相同
//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)")
}
map
和 flatMap
都在 ImplicitlyUnwrappedOptional
上定义,但根据文档,它们的定义(显然)不同:
func map(f: @noescape (T) -> U) -> U!
If self == nil, returns nil. Otherwise, returns f(self!).
func flatMap(f: @noescape (T) -> U!) -> U!
Returns f(self)! iff self and f(self) are not nil.
我试着用一个简单的例子来使用它们:
let number: Int? = 1
let res1 = number.map { [=11=] + 1 }.map { [=11=] + 1 }
let res2 = number.flatMap { [=11=] + 1 }.flatMap { [=11=] + 1 }
res1 //3
res2 //3
但即使 number
是 nil.
,他们也产生了相同的结果
所以我的问题是,如果我将 map
或 flatMap
应用于 ImplicitlyUnwrappedOptional
,它们之间的实际区别是什么?我应该选择哪一个而不是另一个?
(备注:答案已更新,以反映Swift3及以后的语法变化,例如ImplicitlyUnwrappedOptional
的废除。)
Optional.map()
and 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 { [=11=] + 1 }
print(res1) // Optional(2)
number
的类型为 Int?
,闭包类型推断为 (Int) -> Int
。 U
是 Int
,return 值的类型是 Int?
。 number
不是 nil
,所以它被解包并传递 1
被传递给闭包。闭包 returns 2
和 map
returns Optional(2)
。如果 number
是 nil
那么结果将是 nil
.
现在我们考虑使用“flatMap”的第二个示例的简化版本:
let number: Int? = 1
let res2 = number.flatMap { [=12=] + 1 }
print(res2) // Optional(2)
flatMap
期望 (Wrapped) -> U?
类型的闭包,但 { [=38=] + 1 }
不是 return 可选的。为了使其编译,编译器将其转换为
let res2 = number.flatMap { return Optional([=13=] + 1) }
现在闭包的类型为 (Int) -> Int?
,U
又是 Int
。同样,number
被解包并传递给闭包。闭包 returns Optional(2)
也是来自 flatMap
的 return 值。如果 number
是 nil
或 如果闭包 return nil
那么结果将是 nil
.
所以这些调用之间确实没有区别:
let res1 = number.map { [=14=] + 1 }
let res2 = number.flatMap { [=14=] + 1 }
然而,这不是 flatMap
的目的。一个更现实的例子是
func foo(_ s : String?) -> Int? {
return s.flatMap { Int([=15=]) }
}
print(foo("1")) // Optional(1)
print(foo("x")) // nil (because `Int([=15=])` returns nil)
print(foo(nil)) // nil (because the argument is nil)
通常,map
采用类型 (Wrapped) -> U
的闭包并转换
Optional<Wrapped>.none --> Optional<U>.none
Optional<Wrapped>.some(wrapped) --> Optional<U>.some(transform(wrapped))
flatMap
采用类型 (Wrapped) -> U?
的闭包并转换
Optional<Wrapped>.none --> Optional<U>.none
Optional<Wrapped>.some(wrapped) --> transform(wrapped)
这里transform(wrapped)
也可以是Optional<U>.none
如果(如在您的示例中)调用 flatMap
时使用的闭包 not return 是可选的,那么编译器会将其转换为可选的自动,和 map
没有区别了。
This is not possible with
map()
where the mapping closure has the signature(T) -> U
.
这不太正确。在我看来,Martin R 的回答并没有完全触及问题的核心,即文档没有正确描述 map
和 flatMap
之间的区别。
区别在于不是他们采取什么样的闭包。每个人都会愉快地接受一个产生非可选的闭包或一个产生可选的闭包——不管文档怎么说,尽管他们的声明有所不同。所有这些表达式编译:
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
这就是map和flatMap仅的区别。
flatMap
解析嵌套的可选值,而 map
不解析。
平面图
var temp: Int? = 3
var flag: Bool = false
print(temp.flatMap { [=10=] < 5 ? 1 : nil } ?? .zero)
// output: 1
地图
var temp: Int? = 3
var flag: Bool = false
print(temp.map { [=11=] < 5 ? 1 : nil } ?? .zero)
// output: Optional(Optional(1))
Swift 可选地图 vs flatMap
让我们创建自己的 Optional 类型的简单实现并实现 map
和 flatMap
函数
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
- 可以 return 可选 可选flatMap
- 可以平 Optional Optional 到 Optional
Optional.map { () -> T } -> Optional<T>
Optional.map { () -> Optional<T> } -> Optional<Optional<T>>
Optional.flatMap { () -> Optional<T> } -> Optional<T>
map
:transformed
函数的 returned 值被封装到 map
函数的 returned 值的 Optional
中
flatMap
:returntransformed
函数的值与 returnflatMap
函数
//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)")
}