在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编译器不允许这样做。
有什么建议吗?
答案 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)
}
}
您运气不好,因为您无法使用可选链接。
你想要一个很酷的单线吗?
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 { ... }
整件事的结果是一个可选值,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
作为最后一点,将函数转换为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)?'
有关解决方法的任何建议吗?