【问题标题】:Swift Functional Programming - "Optional Bind" vs. "Optional Map"Swift 函数式编程 - “可选绑定”与“可选映射”
【发布时间】:2015-03-02 19:01:00
【问题描述】:

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

并且还指导我们定义一个可选的绑定函数。 注意:本书使用运算符&gt;&gt;=,但我选择使用命名函数,因为它可以帮助我看到相似之处。

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 的函数

“嵌套”这些函数调用的结果伤了我的脑筋:

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 函数编译失败,说明:

    'Int??'不能转换为 'Int?'

我觉得我很接近理解这里发生了什么(可选整数再次被包装在一个可选中?但是如何?为什么?嗯?),但我也离理解还很远,我并不完全确定一个聪明的问题要问。

【问题讨论】:

    标签: swift


    【解决方案1】:
    • 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 的实现,它们做同样的事情。取一个可选项和返回一个可选项的东西,并从中获得一个单一级别的“可选性”。

    这是一个真正常见的问题。 Haskell 为它提供了一个内置的运算符(&gt;&gt;=)。如果你使用方法而不是函数,那么 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。为什么名字不一样?好吧,事实证明“先映射然后展平”相当于另一种称为 monadic bind 的想法,它以不同的方式思考这个问题。是的,单子和所有这些。如果您将T? 视为一个monad(它确实如此),那么flatMap 原来是所需的绑定操作。 (所以“bind”是一个更通用的术语,适用于所有 monad,而“flat map”指的是实现细节。我发现“flat map”首先教人更容易,但是 YMMV。)

    如果您想要这个讨论的更长版本以及它如何适用于Optional 以外的其他类型,请参阅Flattenin' Your Mappenin'

    (*) .? 也可以是 map,具体取决于您传递的内容。如果你通过T-&gt;U?,那么它就是flatMap。如果你传递了T-&gt;U,那么你可以将它视为map,或者你仍然认为它是flatMap,其中U 被隐式提升为U?(Swift 会自动执行)。

    【讨论】:

      【解决方案2】:

      使用更详细的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 -&gt; U) -&gt; 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 -&gt; U,其计算结果为Int -&gt; 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 -&gt; U? 闭包,并在boundInternal 中有一个Int? 返回值。所以在这种情况下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
      }
      

      您的困惑可能来自于变量可以作为可选项“提升”的方式。使用单层时很容易看到:

      func optionalOpposite(num: Int?) -> Int? {
          if let num = num {
              return -num
          }
          return nil
      }
      

      optionalOpposite 可以使用Int? 类型的变量调用,就像它明确期望的那样,或者Int 类型的非可选变量。在第二种情况下,非可选变量在调用期间被隐式转换为可选(即提升)。

      map(x: T, f: T -&gt; U) -&gt; U? 正在提升其返回值。由于f 被声明为T -&gt; U,它永远不会返回可选的U?。然而map 的返回值为U? 意味着f(x) 在返回时被提升为U?

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多