在我最近接触Python之后,我学会了以 X if C else Y
的形式欣赏其conditional expressions的可读性。
当经典ternary conditional operator ?:具有作为第一个参数的条件时,感觉就像赋值完全是关于选择的。当您尝试嵌套多个三元运算符时会变得很难看......当条件在第一个表达式之后移动时,感觉更像是函数的数学定义。我发现这有时有助于提高代码清晰度。
作为code kata,我想在Swift中实现python样式的条件表达式。似乎唯一可以让我获得所需语法的工具是自定义运算符。它们必须由符号组成。 Math symbols in Unicode将double turnstile符号从逻辑标记为TRUE ⊨
,划掉版本不是 ⊭
...所以我选择 ==|
和 |!=
。到目前为止,在争取一段时间找到正确的优先权后,我有这个:
// Python-like conditional expression
struct CondExpr<T> {
let cond: Bool
let expr: () -> T
}
infix operator ==| : TernaryPrecedence // if/where
infix operator |!= : TernaryPrecedence // else
func ==|<T> (lhs: @autoclosure () -> T, rhs: CondExpr<T>) -> T {
return rhs.cond ? lhs() : rhs.expr()
}
func |!=<T> (lhs: Bool, rhs: @escaping @autoclosure () -> T) -> CondExpr<T> {
return CondExpr<T>(cond: lhs, expr: rhs)
}
我知道结果看起来不是非常 swifty 或特别易读,但从好的方面来看,即使表达式分布在多行上,这些运算符也能正常工作。
let e = // is 12 (5 + 7)
1 + 3 ==| false |!=
5 + 7 ==| true |!=
19 + 23
当你用空白创作时,它甚至感觉有点 pythonic :
let included =
Set(filters) ==| !filters.isEmpty |!=
Set(precommitTests.keys) ==| onlyPrecommit |!=
Set(allTests.map { $0.key })
我不喜欢第二个autoclosure必须逃脱。 Nate Cook's answer about custom ternary operators in Swift 2使用了currying语法,这已不再是Swift 3 ......而且我认为这在技术上也是一个逃避关闭。
有没有办法在不逃避关闭的情况下完成这项工作? 它甚至重要吗?也许Swift编译器足够聪明,可以在编译期间解决这个问题,因此它没有运行时影响?
答案 0 :(得分:7)
很棒的问题:)
如果条件为假,则将结果存储为可选,而不是存储表达式。根据一些评论进行编辑,产生了一些更清晰的代码。
infix operator ==| : TernaryPrecedence // if/where
infix operator |!= : TernaryPrecedence // else
func ==|<T> (lhs: @autoclosure () -> T, rhs: T?) -> T {
return rhs ?? lhs()
}
func |!=<T> (lhs: Bool, rhs: @autoclosure () -> T) -> T? {
return lhs ? nil : rhs()
}
答案 1 :(得分:0)
我对如何构建像Python这样的三元运算符没有很好的答案,但我想指出,通过空格,内置三元运算符可以以非常合理的方式链接。
例如,对于if else
块,您可以编写如下内容:
//assuming the return value is being assigned to variable included
if !filters.isEmpty {
return Set(filters)
} else if onlyPrecommit {
return Set(precommitTests.keys)
} else {
return Set(allTests.map { $0.key })
}
转换为链接的三元运算符给出了这个:
let included =( !filters.isEmpty ? Set(filters)
: onlyPrecommit ? Set(precommitTests.keys)
: Set(allTests.map { $0.key })
)
来自python背景,这一开始并不明显,所以我想与其他人分享。