Swift功能编程 - "可选绑定"与"可选地图"

ede*_*y05 10 swift

我一直在使用Swift中函数式编程,我并没有很好的方法来理解Optionals章节中介绍的概念的差异.

使用选项时的模式往往是:

if let thing = optionalThing {
    return doThing(thing)
}
else {
    return nil
}
Run Code Online (Sandbox Code Playgroud)

这个习惯用标准库函数简洁处理 map

map(optionalThing) { thing in doThing(thing) }
Run Code Online (Sandbox Code Playgroud)

然后,本书继续介绍可选绑定的概念,这是我的差异化能力开始分解的地方.

这本书指导我们定义map功能:

func map<T, U>(optional: T?, f: T -> U) -> U?
{
    if let x = optional {
        return f(x)
    }
    else {
        return nil
    }
}
Run Code Online (Sandbox Code Playgroud)

并指导我们定义可选的绑定功能.注意:本书使用了操作符>>=,但我选择使用命名函数,因为它可以帮助我看到相似之处.

func optionalBind<T, U>(optional: T?, f: T -> U?) -> U?
{
    if let x = optional {
        return f(x)
    }
    else {
        return nil
    }
}
Run Code Online (Sandbox Code Playgroud)

这两种方法的实现看起来都与我相同.两者之间的唯一区别是它们采用的函数参数:

  • map 采用将T转换为U的函数
  • optionalBind 采用将T转换为可选U的函数

"嵌套"这些函数调用的结果会伤害我的大脑:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • addOptionalsBind功能完全符合您的期望.
  • addOptionalsMap函数无法编译说明:

    "诠释?" 不能转换为'Int?'

我觉得我已经接近了解这里发生了什么(并且可选的整数再次被包装在一个可选的?但是怎么样?为什么?胡?),但我也远远不能理解我不完全确定一个聪明的问题.

Rob*_*ier 15

  • map采用将T转换为U的函数
  • optionalBind采用将T转换为可选U的函数

究竟.这就是整个差异.让我们考虑一个非常简单的功能lift().它会转换TT?.(在Haskell中,将调用该函数return,但对于非Haskell程序员而言,这有点过于混乱,而且return是关键字).

func lift<T>(x: T) -> T? {
    return x
}

println([1].map(lift)) // [Optional(1)]
Run Code Online (Sandbox Code Playgroud)

大.现在,如果我们再次这样做:

println([1].map(lift).map(lift)) // [Optional(Optional(1))]
Run Code Online (Sandbox Code Playgroud)

嗯.所以现在我们有一个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)]
Run Code Online (Sandbox Code Playgroud)

真棒.正是我们想要的.你知道,这.map(flatten)发生了很多,所以让我们给它一个名字:( flatMap这就是Scala所谓的语言).几分钟的演奏应该向你证明,实现flatMap()完全是实现,bindOptional他们做同样的事情.选择一个可选的东西,返回一个可选项,然后从中获取一个单独的"optional-ness".

这是一个非常普遍的问题.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")))
Run Code Online (Sandbox Code Playgroud)

?.真的只是flatMap(*)这真的是公正的bindOptional.为什么不同的名字?好吧,事实证明,"map and then flatten"相当于另一个叫monadic bind的想法,它以不同的方式思考这个问题.是的,单子和所有这些.如果你认为T?它是一个monad(它是),那么flatMap结果证明是必需的绑定操作.(所以"bind"是一个更通用的术语,适用于所有monad,而"flat map"指的是实现细节.我发现"平面地图"更容易教人们,但是YMMV.)

如果您想要更长版本的讨论以及如何将其应用于其他类型Optional,请参阅Flattenin'Wave Mappenin'.

(*).?也可以map取决于你传递的内容.如果你通过T->U?那么它flatMap.如果你通过T->U,你可以把它看成是map,或者你仍然认为它是的flatMap,其中U被隐式提升为U?(斯威夫特会自动完成).


Nat*_*ook 5

通过更详细的实施可能会更清楚地发生什么addOptionalsMap.让我们从最里面的调用开始 - map而不是你在那里,让我们使用它:

let mappedInternal: Int? = map(optionalY) { (y: Int) -> Int in
    return x + y
}
Run Code Online (Sandbox Code Playgroud)

提供的闭包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
}
Run Code Online (Sandbox Code Playgroud)

在这里我们可以mappedInternal从上面看到我们的价值,但还有一些类型未定义.map有签名(T?, T -> U) -> U?,所以我们只需要找出TU在这种情况下.我们知道闭包的返回值mappedInternal是一个Int?,所以UInt?在这里.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
}
Run Code Online (Sandbox Code Playgroud)

闭包是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
}
Run Code Online (Sandbox Code Playgroud)

我们来看看???这个版本的那些类型.因为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
}
Run Code Online (Sandbox Code Playgroud)

您的混淆可能来自变量可以作为选项"解除"的方式.使用单个图层时很容易看到:

func optionalOpposite(num: Int?) -> Int? {
    if let num = num {
        return -num
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)

optionalOpposite可以使用类型的变量(Int?如显式期望的)或类型的非可选变量来调用Int.在第二种情况下,非可选变量在调用期间被隐式转换为可选(即,提升).

map(x: T, f: T -> U) -> U?正在提升其回报价值.由于f声明为T -> U,它永远不会返回一个可选项U?.然而,返回值mapU?f(x)被提升到U?上的回报.

在你的例子中,内部闭包返回x + y,一个Int被提升到一个Int?.然后再次提升该值以Int??导致类型不匹配,因为您已声明addOptionalsMap返回一个Int?.