Swift,可选解包,反转if条件

5

假设我有一个返回可选值的函数。如果成功,返回值;如果出错,返回nil:

func foo() -> Bar? { ... }

我可以使用以下代码来使用此功能:
let fooResultOpt = foo()
if let fooResult = fooResultOpt {
    // continue correct operations here
} else {
    // handle error
}

然而对于任何非琐碎的代码,这种方法存在一些问题:

  1. 错误处理是在最后执行的,很容易会漏掉某些内容。因此,当错误处理代码跟随函数调用时,效果更佳。

  2. 正确的操作代码需要缩进一级。如果我们有另一个要调用的函数,我们必须再进行一次缩进。

C语言中,我们通常可以这样写:

Bar *fooResult = foo();
if (fooResult == null) {
    // handle error and return
}
// continue correct operations here

我发现两种方法可以用Swift实现类似的代码风格,但都不太理想。
let fooResultOpt = foo()
if fooResult == nil {
    // handle error and return
}
// use fooResultOpt! from here
let fooResult = fooResultOpt! // or define another variable

如果我到处都写“!”,那样看起来很不好。我可以引入另一个变量,但那也不太好看。理想情况下,我希望看到以下内容:
if !let fooResult = foo() {
    // handle error and return
}
// fooResult has Bar type and can be used in the top level

我在规范中有所遗漏还是有其他方法来编写外观良好的Swift代码?
5个回答

2
这就是模式匹配的全部内容,也是用于此工作的工具:
let x: String? = "Yes"

switch x {
case .Some(let value):
  println("I have a value: \(value)")
case .None:
  println("I'm empty")
}
< p > if-let表达式仅在您不需要两个条件分支时才使用,是一种方便的写法。

谢谢,但是你的解决方案与 if (let value = x) { ... } else { ... } 的语法没有区别,并且具有我在问题中提到的所有缺点。 - vbezhenar

2
您的假设是正确的 - 在Swift中没有“否定的if-let”语法。
我猜这可能是为了保持语法的完整性。在整个Swift中(以及其他C风格的语言中通常也是如此),如果您有一个可以绑定本地符号(即命名新变量并给它们赋值)并且可以具有块体(例如,if、while、for)的语句,则这些绑定将作用于该块。让一个块语句将符号绑定到其封闭范围而不是块范围会不一致。
不过,这仍然是一个值得思考的合理事情 - 我建议提交错误报告并看看苹果公司对此的处理方式。

1
如果你正在编写一组执行相同转换序列的函数,例如在处理REST调用返回的结果时(检查响应不为空、检查状态、检查应用程序/服务器错误、解析响应等),我的做法是创建一个管道,在每个步骤中转换输入数据,并在最后返回 nil 或特定类型的转换结果。
我选择了>>>自定义运算符,它直观地表示数据流,但当然可以选择您自己的运算符。
infix operator >>> { associativity left }

func >>> <T, V> (params: T?, next: T -> V?) -> V? {
    if let params = params {
        return next(params)
    }
    return nil
}

运算符是一个函数,它接收某种类型的值和一个将该值转换为另一种类型的闭包作为输入。如果该值不为空,函数将调用该闭包并传递该值,然后返回其返回值。如果该值为nil,则运算符返回nil
可能需要一个示例,假设我有一个整数数组,并想按顺序执行以下操作:
  • 对数组中的所有元素求和
  • 计算2的幂
  • 除以5并返回整数部分和余数
  • 将上述2个数字相加
这些是4个函数:
func sumArray(array: [Int]?) -> Int? {
    if let array = array {
        return array.reduce(0, combine: +)
    }

    return nil
}

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

func module5(num: Int?) -> (Int, Int)? {
    if let num = num {
        return (num / 5, num % 5)
    }
    return nil
}

func sum(params: (num1: Int, num2: Int)?) -> Int? {
    if let params = params {
        return params.num1 + params.num2
    }
    return nil
}

这就是我会使用的方法:

let res: Int? = [1, 2, 3] >>> sumArray >>> powerOf2 >>> module5 >>> sum

这个表达式的结果可能是nil,也可能是管道最后一个函数定义的类型的值。在上面的例子中,它是一个Int。
如果你需要更好的错误处理,可以定义一个类似这样的枚举:
enum Result<T> {
    case Value(T)
    case Error(MyErrorType)
}

在上述函数中,将所有可选项替换为Result<T>,返回Result.Error()而不是nil。请保留HTML标签。

谢谢您的回答,这是一个不错的方法,但它似乎非常非命令式,并且在一般情况下很难扩展和修改。它可能非常适合某些自然数据流的情况。 - vbezhenar
是的,它不是通用的方法,而且它来自函数式编程。也许它不是解决您特定问题的正确方案,但我希望您将来会发现它有用;-) - Antonio

1
我找到了一种看起来比其他方法更好的方式,但它使用了不推荐的语言特性。
问题中使用的代码示例:
let fooResult: Bar! = foo();
if fooResult == nil {
    // handle error and return
}
// continue correct operations here

fooResult可以像普通变量一样使用,不需要使用"?"或"!"后缀。

苹果文档表示:

当可选值在第一次定义后立即确认存在,并且可以肯定地假定在此后的每个点上都存在时,隐式解包可选项非常有用。Swift中隐式解包可选项的主要用途是在类初始化期间使用,如无主引用和隐式解包可选属性中所述。


0
以下怎么样:
func foo(i:Int) ->Int? {
    switch i {
    case 0: return 0
    case 1: return 1
    default: return nil
    }
}

var error:Int {
    println("Error")
   return 99
}

for i in 0...2 {
    var bob:Int = foo(i) ?? error
    println("\(i) produces \(bob)")
}

结果会产生以下输出:

0 produces 0
1 produces 1
Error
2 produces 99

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接