2017-05-04 8 views
8

Nach meiner jüngsten Exposition gegenüber Python, lernte ich die Lesbarkeit seiner conditional expressions mit der Form X if C else Y schätzen zu schätzen.Python-Stil konditionalen Ausdruck in Swift 3

Wenn die klassische ternary conditional operator ?: die Bedingung als erstes Argument hat, fühlt es sich an, als ob die Aufgabe sich auf die Wahl bezieht. Es wird hässlich, wenn Sie versuchen, mehrere ternäre Operatoren zu verschachteln ... Wenn die Bedingung nach dem ersten Ausdruck verschoben wird, fühlt es sich eher wie eine mathematische Definition einer Funktion an. Ich finde das hilft manchmal bei der Code-Klarheit.

Wie code kata wollte ich in Swift Python-Stil bedingten Ausdruck implementieren. Es scheint, als ob das einzige Werkzeug, das mir die erforderliche Syntax bringen kann, benutzerdefinierte Operatoren sind. Sie müssen aus Symbolen bestehen. Math symbols in Unicode Etikett des double turnstile Symbol von Logik als TRUE und Version durchgestrichen ist NICHT WAHR ... so ging ich mit ==| und |!=. Bisher nachdem eine Zeit lang im Kampf gegen den richtigen Vorrang zu finden, ich habe dies:

// 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) 
} 

Ich weiß, dass das Ergebnis nicht sehr sieht swifty oder besonders lesbar, aber auf der hellen Seite, diese Operatoren Arbeit auch wenn der Ausdruck über mehrere Zeilen verteilt ist.

let e = // is 12 (5 + 7) 
    1 + 3 ==| false |!= 
    5 + 7 ==| true |!= 
    19 + 23 

Wenn Sie mit Leerzeichen kreativ, es fühlt sich sogar ein wenig pythonic:

let included = 
    Set(filters)    ==| !filters.isEmpty |!= 
    Set(precommitTests.keys) ==| onlyPrecommit |!= 
    Set(allTests.map { $0.key }) 

Ich mag es nicht, dass die zweite autoclosure Flucht werden muss. Nate Cook's answer about custom ternary operators in Swift 2 verwendete Currysyntax, die nicht mehr in Swift 3 ist ... und ich vermute, das war technisch auch eine flüchtige Schließung.

Gibt es eine Möglichkeit, dies zu bewerkstelligen, ohne die Schließung zu umgehen? Ist es überhaupt wichtig? Vielleicht ist der Swift-Compiler schlau genug, um dies während der Kompilierungszeit zu beheben, so dass es keine Auswirkungen auf die Laufzeit hat?

Antwort

7

Große Frage :)

Anstatt den Ausdruck zu speichern, speichern Sie das Ergebnis als optional, wenn die bedingten falsch ist. Bearbeitet basierend auf einigen Kommentaren, was zu etwas saubereren Code führt.

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

Schön. Du könntest diesen zweiten Funktionskörper etwas hübscher machen, vielleicht: https://gist.github.com/woolsweater/4da0dc0bb348fb40a2becb1aec9a2890 –

+0

Danke für dieses Feedback. Ich räume meine Antwort auf. –

+2

Der erste Funktionskörper kann geschrieben werden als 'return rhs ?? lhs() ' –

0

Ich habe keine gute Antwort darauf, wie ein ternären Operator wie Python zu bilden, aber ich mag darauf hinweisen, dass mit Leerzeichen des eingebaute ternäre Operator in einem sehr logisch verkettet werden.

Zum Beispiel mit if else Blöcke könnten Sie so etwas schreiben:

//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 }) 
} 

die mit dem ternären Operator gibt diese verketteten übersetzt:

let included =(!filters.isEmpty ? Set(filters) 
       : onlyPrecommit ? Set(precommitTests.keys) 
           : Set(allTests.map { $0.key }) 
      ) 

aus einem Python-Hintergrund kommt dies nicht offensichtlich war, Zuerst möchte ich es mit anderen teilen.