Swift 3

时间:2016-10-05 18:31:07

标签: swift swift3

我试图在Swift 3中创建一个null-coalescing赋值运算符。换句话说,而不是:

x = x ?? y

我想要这个:

x ??= y

Swift 3似乎不喜欢我的操作员。这是它的定义:

infix operator ??= : AssignmentPrecedence
func ??=(lhs: inout Any?, rhs: @autoclosure () -> Any?) {
    if lhs != nil { return }
    lhs = rhs()
}
var x = 3
x ??= 7 // Cannot convert value of type 'Int' to expected argument type 'inout Any?'.

我在没有@autoclosure的情况下完成了它。我的理解是优先组AssignmentPrecedence已经包含assignment声明,所以这不太可能是问题。

如何在Swift 3中执行此操作?

2 个答案:

答案 0 :(得分:5)

首先,使运算符通用而不是使用Any

infix operator ??= : AssignmentPrecedence
func ??=<T>(lhs: inout T?, rhs: @autoclosure () -> T?) {
    if lhs != nil { return }
    lhs = rhs()
}

其次,左操作数需要是可选的(否则它 无法针对nil)进行测试:

var x: Int? = 3
x ??= 7

答案 1 :(得分:1)

我想出了自己的“空值销售”赋值运算符的“风格”,类似于上面的内容,但是通过添加一个返回类型,您既可以同时进行赋值又可以返回,这与正常情况更加一致'??'行为,如果LHS为空,则从RHS到LHS分配分配。非常适合可重置的懒惰变量。这是我的版本...

// Null-coalescing assignment operator
infix operator ??= : AssignmentPrecedence
@discardableResult
func ??= <T>(lhs:inout T?, rhs:@autoclosure () -> T) -> T {

    if let lhs = lhs {
        return lhs
    }

    let rhsResult = rhs()

    lhs = rhsResult

    return rhsResult
}

有了上面的内容,我现在可以像这样做可重置的懒惰变量了……

private var qCache:Int?
var q:Int {
    return qCache ??= {
        print("Lazy-calculating q...")
        return 44
    }()
}
func resetQ() { qCache = nil }

print("1: q is \(q)")
print("2: q is \(q)")
print("3: q is \(q)")

print("Resetting lazy q...")
resetQ()

print("4: q is \(q)")

输出...

Lazy-calculating q...
1: q is 44
2: q is 44
3: q is 44
Resetting lazy q...
Lazy-calculating q...
4: q is 44

如果您愿意的话,也可以实现setter并分离出“惰性计算”功能...

func lazyCalcQ() -> Int {
    print("Lazy-calculating q...")
    return 44
}    

private var qCache:Int?
var q:Int {
    get { return qCache ??= lazyCalcQ() }
    set { qCache = newValue }
}
func resetQ() { qCache = nil }

更进一步,您可以使用隐式展开的数据类型,以便可以使用nil的赋值进行重置,但始终可以确保从getter获得值。唯一的缺点是您有时仍必须强制将其拆开以使某些警告消失,例如下面的打印语句,但同样,可以保证您有一个值,因此这样做是安全的。

注意:就我个人而言,我更喜欢上面的方法使用非隐式展开版本和额外的“重置”功能-特别是如果它是只读属性-因为它使API更加清晰,但是我将其分享为完整起见,因为它显示了nil-as-reset的创造性用法。

func lazyCalcR() -> Int {
    print("Lazy-calculating r (i.e. the default when reset with 'nil')...")
    return 10
}

private var rCache:Int?
var r:Int! {
    get { return rCache ??= lazyCalcR() }
    set { rCache = newValue }
}

print("1: r is \(r!)")
r += 10
print("2: r is \(r!)")
r += 10
print("3: r is \(r!)")

print("Resetting r to default...")
r = nil

print("4: r is \(r!)")

输出...

Lazy-calculating r (i.e. the default when reset with 'nil')...
1: r is 10
2: r is 20
3: r is 30
Resetting r to default...
Lazy-calculating r (i.e. the default when reset with 'nil')...
4: r is 10

当然,以上都是使用int的简单示例,但是我用它来执行诸如基于边界计算复杂路径之类的事情。

我的下一个尝试是将所有这些隐藏在属性包装器中,从而消除对该操作符本身的需要。