Swift 3中的Python样式条件表达式

时间:2017-05-04 21:33:20

标签: swift

在我最近接触Python之后,我学会了以 X if C else Y 的形式欣赏其conditional expressions的可读性。

当经典ternary conditional operator ?:具有作为第一个参数的条件时,感觉就像赋值完全是关于选择的。当您尝试嵌套多个三元运算符时会变得很难看......当条件在第一个表达式之后移动时,感觉更像是函数的数学定义。我发现这有时有助于提高代码清晰度。

作为code kata,我想在Swift中实现python样式的条件表达式。似乎唯一可以让我获得所需语法的工具是自定义运算符。它们必须由符号组成。 Math symbols in Unicodedouble 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编译器足够聪明,可以在编译期间解决这个问题,因此它没有运行时影响?

2 个答案:

答案 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背景,这一开始并不明显,所以我想与其他人分享。