Swift 3 中的空合并赋值运算符

问题描述 投票:0回答:2

我正在尝试在 Swift 3 中创建一个空合并赋值运算符。换句话说,而不是这个:

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 中执行此操作?

swift swift3
2个回答
8
投票

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

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

2
投票

我想出了我自己的空合并赋值运算符的“风格”,与上面类似,但是通过添加返回类型,您可以同时进行赋值和返回,这更符合正常的“?” ?行为,如果 LHS 为空,则添加从 RHS 到 LHS 的分配。它非常适合可重置的惰性变量。这是我的版本...

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

    if let 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

如果您愿意,您也可以实现设置器以及分离出“惰性计算”函数...

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 的简单示例,但我用它来做一些事情,比如根据边界计算复杂路径等。

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

© www.soinside.com 2019 - 2024. All rights reserved.