Swift Functional Programming - "可选绑定" vs."可选地图"

时间:2015-03-02 19:01:01

标签: swift

我一直在阅读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;

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

2 个答案:

答案 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?,因此我们只需要确定TU在这种情况下的含义。我们知道闭包的返回值mappedInternalInt?,因此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返回值。因此,在这种情况下,TU都可以只是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 + yInt被提升为Int?。然后该值再次提升为Int??,导致类型不匹配,因为您已声明addOptionalsMap返回Int?