避免连续"如果让" Swift中的声明

时间:2014-10-31 09:57:18

标签: swift

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

if let obj = optionalObj
{
}

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

if let obj = optionalObj
{
    if let a = obj.a
    {
        if let b = a.b
        {
            // do stuff
        }
    }
}

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

我会尝试类似的事情:

if let obj = optionalObj && if let a = obj.a && if let b = a.b
{
    // do stuff
}

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

有什么建议吗?

4 个答案:

答案 0 :(得分:27)

更新

在swift 1.2中你可以做到

if let a = optA, let b = optB {
  doStuff(a, b)
}

原始答案

在您的具体情况下,您可以使用可选链接:

if let b = optionaObj?.a?.b {
  // do stuff
}

现在,如果您需要执行类似

的操作
if let a = optA {
  if let b = optB {
    doStuff(a, b)
  }
}

您运气不好,因为您无法使用可选链接。

TL;医生

你想要一个很酷的单线吗?

doStuff <^> optA <*> optB

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

幸运的是,使用函数式编程方法很容易解决这个问题。您可以使用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
    }
}

然后我们可以使用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
    }
}

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

doStuff <^> optA <*> optB

如果doStuff以咖喱形式(见下文),即

,这将有效。
func doStuff(a: A)(b: B) -> C { ... }

整件事的结果是一个可选值,nildoStuff的结果

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

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

作为最后一点,将函数转换为curried形式非常简单。例如,如果你有一个函数采用两个参数:

func curry<A, B, C>(f: (A, B) -> C) -> A -> B -> C {
    return { a in { b in f(a,b) } }
}

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

curry(+) <^> optA <*> optC // Some 3

答案 1 :(得分: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...

这些可用于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 ...}

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

答案 2 :(得分:2)

在某些情况下,您可以使用可选链接。举个简单的例子:

if let b = optionalObj?.a?.b {
    // do stuff
}

为了保持你的嵌套并为自己提供相同的变量赋值,你也可以这样做:

if optionalObj?.a?.b != nil {
    let obj = optionalObj!
    let a = obj.a!
    let b = a.b!
}

答案 3 :(得分:1)

在感谢Martin R的一些演讲之后,我找到了一个有趣的解决方法:https://stackoverflow.com/a/26012746/2754218

func unwrap<T, U>(a:T?, b:U?, handler:((T, U) -> ())?) -> Bool {
    switch (a, b) {
    case let (.Some(a), .Some(b)):
        if handler != nil {
            handler!(a, b)
        }
        return true
    default:
        return false
    }
}

解决方案很有意思,但如果方法使用可变参数,那会更好。

我天真地开始创造这样一种方法:

extension Array
{
    func find(includedElement: T -> Bool) -> Int?
    {
        for (idx, element) in enumerate(self)
        {
            if includedElement(element)
            {
                return idx
            }
        }

        return nil
    }
}

func unwrap<T>(handler:((T...) -> Void)?, a:T?...) -> Bool
{

    let b : [T!] = a.map { $0 ?? nil}

    if b.find({ $0 == nil }) == nil
    {
        handler(b)
    }
}

但是我在编译器中遇到了这个错误:Cannot convert the expression's type '[T!]' to type '((T...) -> Void)?'

有关解决方法的任何建议吗?