2011-02-09 17 views
73

sto cercando di evitare di costrutti come questo:ternario Operatore simile a:?

val result = this.getClass.getSimpleName 
if (result.endsWith("$")) result.init else result 

Ok, in questo esempio il ramo then e else sono semplici, ma si può quelli complessi di immagine. ho costruito il seguente:

object TernaryOp { 
    class Ternary[T](t: T) { 
    def is[R](bte: BranchThenElse[T,R]) = if (bte.branch(t)) bte.then(t) else bte.elze(t) 
    } 
    class Branch[T](branch: T => Boolean) { 
    def ?[R] (then: T => R) = new BranchThen(branch,then) 
    } 
    class BranchThen[T,R](val branch: T => Boolean, val then: T => R) 
    class Elze[T,R](elze: T => R) { 
    def :: (bt: BranchThen[T,R]) = new BranchThenElse(bt.branch,bt.then,elze) 
    } 
    class BranchThenElse[T,R](val branch: T => Boolean, val then: T => R, val elze: T => R) 
    implicit def any2Ternary[T](t: T) = new Ternary(t) 
    implicit def fct2Branch[T](branch: T => Boolean) = new Branch(branch) 
    implicit def fct2Elze[T,R](elze: T => R) = new Elze(elze) 
} 

definito che, posso sostituire il semplice esempio di cui sopra con:

this.getClass.getSimpleName is {s: String => s.endsWith("$")} ? {s: String => s.init} :: {s: String => s} 

Ma come posso liberarmi della s: String =>? Voglio qualcosa del genere:

this.getClass.getSimpleName is {_.endsWith("$")} ? {_.init} :: {identity} 

Immagino che il compilatore abbia bisogno di cose extra per inferire i tipi.

+0

Dal momento che in realtà non ho questo nella mia risposta - la ragione per cui' avere problemi è che l'inferenza di tipo funziona meglio da sinistra a destra, ma vincoli i token insieme da destra a sinistra a causa della precedenza degli operatori. Se fai tutte le parole delle tue affermazioni (con la stessa precedenza) e cambi il modo in cui le cose si raggruppano, otterrai l'inferenza che desideri. (Cioè si dovrebbero avere classi 'HasIs',' IsWithCondition', 'ConditionAndTrueCase' che creerebbero parti dell'espressione da sinistra a destra.) –

+0

Ho presumibilmente inconsciamente il modo di inferenza di tipo da sinistra a destra, ma bloccato con la precedenza degli operatori e associatività dei nomi dei metodi, specialmente iniziando con '?' prima di qualsiasi altro char alfanum come nome del metodo prima char e a ':' per associatività sinistra. Quindi devo ripensare ai nuovi nomi dei metodi per ottenere un'inferenza di tipo funzionante da sinistra a destra. Grazie! –

risposta

20

possiamo combinare How to define a ternary operator in Scala which preserves leading tokens? con la risposta a Is Option wrapping a value a good pattern? per ottenere

scala> "Hi".getClass.getSimpleName |> {x => x.endsWith("$") ? x.init | x} 
res0: String = String 

scala> List.getClass.getSimpleName |> {x => x.endsWith("$") ? x.init | x} 
res1: String = List 

È questo sufficiente per le vostre esigenze?

+0

Questo è molto vicino a quello che ho in mente. buon approccio. Ci penserò. Il mio motivo per evitare il primissimo codice era di essere più conciso nel non avere un 'val' temporaneo per una seguente istruzione' if': è intelligibile in una riga, proprio come si ha in mente. –

13

Rex Kerr’s answer espresso in Scala di base:

"Hi".getClass.getSimpleName match { 
    case x if x.endsWith("$") => x.init 
    case x => x 
} 

anche se non sono sicuro di quale parte del costrutto if-else si desidera ottimizzare.

+0

molto dritto. a volte ci si dimentica delle dichiarazioni match/case di uso quotidiano. Ho solo attenuto all'idioma ternario di una riga, se non altro, ma è davvero un modo comprensibile di risolvere. –

+0

Pattern Matching scala facilmente su più di due rami. – Raphael

100

Da Tony Morris' Lambda Blog:

ho sentito questa domanda un sacco. Sì, lo fa. Invece di c ? p : q, è scritto if(c) p else q.

Potrebbe non essere preferibile. Forse ti piacerebbe scriverlo usando la stessa sintassi di Java. Purtroppo, non puoi. Questo perché : non è un identificatore valido. Non temere, | è! Ti accontenterai di questo?

c ? p | q 

Quindi è necessario il seguente codice. Notare le annotazioni call-by-name (=>) sugli argomenti. Questa strategia di valutazione è necessaria per riscrivere correttamente l'operatore ternario di Java. Questo non può essere fatto in Java stesso.

case class Bool(b: Boolean) { 
    def ?[X](t: => X) = new { 
    def |(f: => X) = if(b) t else f 
    } 
} 

object Bool { 
    implicit def BooleanBool(b: Boolean) = Bool(b) 
} 

Ecco un esempio utilizzando l'operatore new che abbiamo appena definito:

object T { val condition = true 

    import Bool._ 

    // yay! 
    val x = condition ? "yes" | "no" 
} 

Buon divertimento;)

+0

sì, l'ho già visto prima, ma la differenza è che a ho il valore (valutato) della mia prima espressione come argomento nella clausola 'then' e' else'. –

+0

@Imre: ho corretto il collegamento e copiato il contenuto. – Landei

+2

Ho preso l'approccio 'if (c) p else q' ... la mancanza di bretelle mi rende un po 'scomodo ma è solo una cosa di stile – rjohnston

0

Dal: di per sé non sarà un operatore valida a meno che non si sono ok evitandoti sempre con i tick posteriori :, potresti andare con un altro personaggio, ad es. "|" come in una delle risposte sopra. Ma come circa Elvis con il pizzetto? ::

implicit class Question[T](predicate: => Boolean) { 
    def ?(left: => T) = predicate -> left 
} 
implicit class Colon[R](right: => R) { 
    def ::[L <% R](pair: (Boolean, L)): R = if (q._1) q._2 else right 
} 
val x = (5 % 2 == 0) ? 5 :: 4.5 

Naturalmente questo ancora una volta non funzionerà se i valori sono liste, dal momento che hanno :: operator stessi.