避免在Swift中连续"if let"声明

Jea*_*ent 18 swift

在Swift中,我使用if let声明来检查我的对象是否不是nil

if let obj = optionalObj
{
}
Run Code Online (Sandbox Code Playgroud)

但有时,我必须面对连续的if let声明

if let obj = optionalObj
{
    if let a = obj.a
    {
        if let b = a.b
        {
            // do stuff
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在寻找避免连续if let声明的方法.

我会尝试类似的东西:

if let obj = optionalObj && if let a = obj.a && if let b = a.b
{
    // do stuff
}
Run Code Online (Sandbox Code Playgroud)

但swift编译器不允许这样做.

有什么建议吗?

Gab*_*lla 27

更新

在swift 1.2中你可以做到

if let a = optA, let b = optB {
  doStuff(a, b)
}
Run Code Online (Sandbox Code Playgroud)

原始答案

在您的特定情况下,您可以使用可选链接:

if let b = optionaObj?.a?.b {
  // do stuff
}
Run Code Online (Sandbox Code Playgroud)

现在,如果你需要做类似的事情

if let a = optA {
  if let b = optB {
    doStuff(a, b)
  }
}
Run Code Online (Sandbox Code Playgroud)

你运气不好,因为你不能使用可选的链接.

TL; 博士

你喜欢酷的单线吗?

doStuff <^> optA <*> optB
Run Code Online (Sandbox Code Playgroud)

继续阅读.对于它看起来多么吓人,这真的很强大,并不像看起来那么疯狂.

幸运的是,这是一个使用函数式编程方法轻松解决的问题.您可以使用Applicative抽象并提供一种apply方法来组合多个选项.

以下是一个例子,摘自http://robots.thoughtbot.com/functional-swift-for-dealing-with-optional-values

首先,我们需要一个函数,仅在函数包含某些内容时才将函数应用于可选值

// this function is usually called fmap, and it's represented by a <$> operator
// in many functional languages, but <$> is not allowed by swift syntax, so we'll
// use <^> instead
infix operator <^> { associativity left }

func <^><A, B>(f: A -> B, a: A?) -> B? {
    switch a {
    case .Some(let x): return f(x)
    case .None: return .None
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我们可以使用apply组合多个选项,我们会调用它,<*>因为我们很酷(我们知道一些Haskell)

// <*> is the commonly-accepted symbol for apply
infix operator <*> { associativity left }

func <*><A, B>(f: (A -> B)?, a: A?) -> B? {
    switch f {
    case .Some(let value): return value <^> a
    case .None: return .None
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以重写我们的例子

doStuff <^> optA <*> optB
Run Code Online (Sandbox Code Playgroud)

这将是有效的,只要doStuff是以咖喱形式(见下文),即

func doStuff(a: A)(b: B) -> C { ... }
Run Code Online (Sandbox Code Playgroud)

整个事物的结果是一个可选值,nil或者是结果doStuff

这是一个完整的例子,您可以在操场上尝试

func sum(a: Int)(b: Int) -> Int { return a + b }

let optA: Int? = 1
let optB: Int? = nil
let optC: Int? = 2

sum <^> optA <*> optB // nil
sum <^> optA <*> optC // Some 3
Run Code Online (Sandbox Code Playgroud)

最后要注意的是,将函数转换为curried形式非常简单.例如,如果您有一个带两个参数的函数:

func curry<A, B, C>(f: (A, B) -> C) -> A -> B -> C {
    return { a in { b in f(a,b) } }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以讨论任何双参数函数,+例如

curry(+) <^> optA <*> optC // Some 3
Run Code Online (Sandbox Code Playgroud)

  • 这似乎是一个复杂的答案,但恕我直言,这是思考解决方案的正确方法.我们不应该只使用Objective-C代码的行端口. (4认同)
  • @BradLarson哦是的,monadic bind绝对是一种有效的方法,我在其他语言中广泛使用它.但是我有两个问题:monad比应用程序更强大,我通常倾向于选择功能最弱的抽象; 第二,swift缺乏do-blocks(haskell)或for-blocks(scala)提供的句法糖,所以使用它比使用appaste更麻烦.两者都是有效的方法:) (2认同)
  • @JeanLebrument不是,这是我在上述评论中的观点.语法支持尚未准备好进行monadic绑定 (2认同)

Pyr*_*ola 8

我前段时间写了一篇关于替代品的小文章:https://gist.github.com/pyrtsa/77978129090f6114e9fb

其他答案中尚未提及的一种方法,我有点喜欢,是添加一堆重载every函数:

func every<A, B>(a: A?, b: B?) -> (A, B)? {
    switch (a, b) {
        case let (.Some(a), .Some(b)): return .Some((a, b))
        default:                       return .None
    }
}

func every<A, B, C>(a: A?, b: B?, c: C?) -> (A, B, C)? {
    switch (a, b, c) {
        case let (.Some(a), .Some(b), .Some(c)): return .Some((a, b, c))
        default:                                 return .None
    }
}

// and so on...
Run Code Online (Sandbox Code Playgroud)

这些可以在if let语句,case表达式以及optional.map(...)链中使用:

// 1.
var foo: Foo?
if let (name, phone) = every(parsedName, parsedPhone) {
    foo = ...
}

// 2.
switch every(parsedName, parsedPhone) {
    case let (name, phone): foo = ...
    default: foo = nil
}

// 3.
foo = every(parsedName, parsedPhone).map{name, phone in ...}
Run Code Online (Sandbox Code Playgroud)

必须添加重载every是样板,但只需要在库中完成一次.类似地,使用Applicative Functor方法(即使用<^><*>运算符),您需要以某种方式创建curried函数,这也会在某处产生一些样板.

  • 值得注意的是,你可以从一对形成算子和加布里埃尔提到的应用内容中得到每一个.`every2`是`liftA2(,)`,`every3`是`liftA3(,,)` (2认同)
  • 正如CodaFi所提到的,如果你愿意的话,可以在我的答案之上建立"every"实现.一旦我们有适当的机制,其余的只是聪明的语法;) (2认同)