Operatore ternario simile a?:


94

Sto cercando di evitare costrutti come questo:

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

Ok, in questo esempio i rami thene elsesono semplici, ma puoi immaginare quelli complessi. Ho costruito quanto segue:

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 questo, posso sostituire il semplice esempio sopra con:

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

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

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

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


Dal momento che in realtà non avevo questo nella mia risposta, il motivo per cui hai problemi è che l'inferenza del tipo funziona meglio da sinistra a destra, ma stai legando i tuoi token insieme da destra a sinistra a causa della precedenza degli operatori. Se fai tutte le tue dichiarazioni parole (con la stessa precedenza) e cambi il modo in cui le cose si raggruppano, otterrai l'inferenza che desideri. (Ie si avrebbe HasIs, IsWithCondition, ConditionAndTrueCasele classi che si accumulerebbe parti l'espressione da sinistra a destra.)
Rex Kerr

Ho inconsciamente presunto la via dell'inferenza del tipo da sinistra a destra, ma mi sono bloccato con la precedenza degli operatori e l'associatività dei nomi dei metodi, specialmente a partire da ?prima di qualsiasi altro carattere alfanum come primo carattere del nome del metodo e a :per associatività a sinistra. Quindi devo ripensare ai nuovi nomi dei metodi per ottenere l'inferenza del tipo che funziona da sinistra a destra. Grazie!
Peter Schmitz

Risposte:


28

Possiamo combinare Come definire un operatore ternario in Scala che preserva i token iniziali? con la risposta a Opzione che avvolge un valore è un buon modello? 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

È adeguato alle tue esigenze?


Questo è molto vicino a quello che ho in mente. bel approccio. Ci penserò. La mia ragione per evitare il primissimo codice era di essere più conciso nel non avere una temporanea valper una seguente ifaffermazione: Fallo intelligibile in una riga, proprio come si ha in mente.
Peter Schmitz

125

Dal blog Lambda di Tony Morris :

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

Questo 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, lo |è! Ti accontenteresti di questo?

c ? p | q

Allora avrai bisogno del 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 che utilizza l'operatore new che abbiamo appena definito:

object T {   val condition = true

  import Bool._

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

Divertiti ;)


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

5
Ho preso l' if(c) p else qapproccio ... la mancanza di parentesi graffe mi mette un po 'a disagio, ma è solo una cosa di stile
rjohnston

17

La risposta di Rex Kerr espressa 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 desideri ottimizzare.


modo molto diretto. a volte ci si dimentica delle dichiarazioni di corrispondenza / caso d'uso quotidiano. Mi sono limitato if then elseall'idioma ternario di una riga , ma è davvero un modo comprensibile per risolvere.
Peter Schmitz

1
Pattern Matching scala facilmente a più di due rami.
Raphael


0

Poiché: di per sé non sarà un operatore valido a meno che tu non sia d'accordo con l'escape sempre :con i segni di spunta indietro , potresti usare un altro carattere, ad esempio "|" come in una delle risposte sopra. Ma che ne dici di 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

Ovviamente anche questo non funzionerà se i valori sono elenchi, poiché hanno l'operatore :: essi stessi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.