我一直在阅读Functional Programming in Swift一书,我并没有真正理解选项章节中介绍的概念差异的好方法。
使用期权时的模式往往是:
if let thing = optionalThing {
return doThing(thing)
}
else {
return nil
}
使用标准库函数map
map(optionalThing) { thing in doThing(thing) }
然后本书继续介绍可选绑定的概念,这是我的差异化能力开始分解的地方。
本书指导我们定义map
函数:
func map<T, U>(optional: T?, f: T -> U) -> U?
{
if let x = optional {
return f(x)
}
else {
return nil
}
}
并指导我们定义可选的绑定功能。 注意:本书使用运算符>>=
,但我选择使用命名函数,因为它可以帮助我看到相似之处。
func optionalBind<T, U>(optional: T?, f: T -> U?) -> U?
{
if let x = optional {
return f(x)
}
else {
return nil
}
}
这两种方法的实现看起来与我相同。两者之间的唯一区别是它们采用的函数参数:
map
采用将T转换为U optionalBind
采用将T转换为可选U &#34;嵌套&#34;的结果这些函数调用伤害了我的大脑:
func addOptionalsBind(optionalX: Int?, optionalY: Int?) -> Int?
{
return optionalBind(optionalX) { x in
optionalBind(optionalY) { y in
x + y
}
}
}
func addOptionalsMap(optionalX: Int?, optionalY: Int?) -> Int?
{
return map(optionalX) { x in
map(optionalY) { y in
x + y
}
}
}
addOptionalsBind
功能完全符合您的预期。addOptionalsMap
函数无法编译声明:
&#39;诠释??&#39;不能转换为&#39; Int?&#39;
答案 0 :(得分:13)
- map采用将T转换为U
的函数- optionalBind采用将T转换为可选U
的函数
完全。这就是整个差异。让我们考虑一个非常简单的函数lift()
。它会将T
转换为T?
。 (在Haskell中,该函数将被称为return
,但对于非Haskell程序员而言,这有点太混乱了,此外,return
是关键字)。
func lift<T>(x: T) -> T? {
return x
}
println([1].map(lift)) // [Optional(1)]
大。如果我们再次这样做会怎么样:
println([1].map(lift).map(lift)) // [Optional(Optional(1))]
嗯。所以现在我们有一个Int??
,这很难对付。我们真的只是有一个级别的可选性。让我们建立一个能够做到这一点的功能。我们将其称为flatten
并将双选项展平为单一可选项。
func flatten<T>(x: T??) -> T? {
switch x {
case .Some(let x): return x
case .None : return nil
}
}
println([1].map(lift).map(lift).map(flatten)) // [Optional(1)]
真棒。正是我们想要的。你知道,.map(flatten)
发生了很多,所以让我们给它一个名字:flatMap
(这就像Scala所说的那样)。几分钟的演奏应该向你证明flatMap()
的实现正是bindOptional
的实现,他们做同样的事情。选择一个可选的东西,然后返回一个可选项,并获得一个单一级别的&#34; optional-ness&#34;出来的。
这是真正的常见问题。 Haskell有一个内置的运算符(>>=
)是如此常见。如果您使用方法而不是函数,那么Swift 还有一个内置的运算符,这很常见。它被称为可选链接(Swift并没有将其扩展到函数,这真是一种耻辱,但Swift比方法更喜欢方法):
struct Name {
let first: String? = nil
let last: String? = nil
}
struct Person {
let name: Name? = nil
}
let p:Person? = Person(name: Name(first: "Bob", last: "Jones"))
println(p?.name?.first) // Optional("Bob"), not Optional(Optional(Optional("Bob")))
?.
实际上只是flatMap
(*),它实际上只是bindOptional
。为什么不同的名字?好吧,事实证明&#34;映射然后变平&#34;相当于另一种叫做monadic bind的想法,它以不同的方式思考这个问题。是的,单子和所有这些。如果您将T?
视为monad(它是),那么flatMap
将成为所需的绑定操作。 (所以&#34; bind&#34;是一个更通用的术语,适用于所有monad,而&#34; flat map&#34;指的是实现细节。我发现&#34;平面地图&#34;更容易先教人,但YMMV。)
如果您想要更长版本的讨论以及它如何应用于Optional
以外的其他类型,请参阅Flattenin' Your Mappenin'。
(*).?
也可以归类为map
,具体取决于您传递的内容。如果您通过了T->U?
,那么它就是flatMap
。如果您通过了T->U
,那么您可以将其视为map
,或者您仍然认为flatMap
被隐含地提升为U
U?
(Swift会自动执行)。
答案 1 :(得分:5)
addOptionalsMap
的更详细的实施可能会更清楚地发生什么。让我们从map
的最里面的电话开始 - 而不是你在那里,让我们改用它:
let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in
return x + y
}
提供给map
的关闭需要Int
并返回Int
,而对map
的调用本身会返回一个可选:Int?
。没有惊喜!让我们向前迈出一步,看看会发生什么:
let mappedExternal: ??? = map(optionalX) { (x: ???) -> ??? in
let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in
return x + y
}
return mappedInternal
}
在这里,我们可以看到上面的mappedInternal
值,但还有一些类型未定义。 map
的签名为(T?, T -> U) -> U?
,因此我们只需要确定T
和U
在这种情况下的含义。我们知道闭包的返回值mappedInternal
是Int?
,因此U
变为Int?
。另一方面,T
可以保持非可选Int
。替代,我们得到这个:
let mappedExternal: Int?? = map(optionalX) { (x: Int) -> Int? in
let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in
return x + y
}
return mappedInternal
}
结束时为T -> U
,评估结果为Int -> Int?
,整个map
表达式最终将Int?
映射到Int??
。不是你的想法!
与使用optionalBind
的版本进行对比,完全按类型指定:
let boundExternal: ??? = optionalBind(optionalX) { (x: ???) -> ??? in
let boundInternal: Int? = optionalBind(optionalY) { (y: Int) -> Int? in
return x + y
}
return boundInternal
}
让我们看看这个版本的???
类型。对于optionalBind
,我们需要T -> U?
关闭,Int?
中的boundInternal
返回值。因此,在这种情况下,T
和U
都可以只是Int
,我们的实现如下所示:
let boundExternal: Int? = optionalBind(optionalX) { (x: Int) -> Int? in
let boundInternal: Int? = optionalBind(optionalY) { (y: Int) -> Int? in
return x + y
}
return boundInternal
}
你的困惑可能来自变量的解除方式&#34;解除了#34;作为选择。使用单个图层时很容易看到:
func optionalOpposite(num: Int?) -> Int? {
if let num = num {
return -num
}
return nil
}
optionalOpposite
可以使用Int?
类型的变量调用,就像它明确期望的那样,或类型为Int
的非可选变量。在第二种情况下,非可选变量在调用期间被隐式转换为可选(即,提升)。
map(x: T, f: T -> U) -> U?
正在提升其返回值。由于f
被声明为T -> U
,因此它永远不会返回可选的U?
。然而,map
作为U?
的返回值意味着f(x)
在返回时被提升到U?
。
在你的例子中,内部闭包返回x + y
,Int
被提升为Int?
。然后该值再次提升为Int??
,导致类型不匹配,因为您已声明addOptionalsMap
返回Int?
。