Ternärer Operator ähnlich?

94

Ich versuche solche Konstrukte zu vermeiden:

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

Ok, in diesem Beispiel sind der thenund else-Zweig einfach, aber Sie können sich komplexe vorstellen. Ich habe folgendes gebaut:

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

Definiert, kann ich das obige einfache Beispiel ersetzen durch:

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

Aber wie kann ich das loswerden s: String =>? Ich möchte so etwas:

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

Ich denke, der Compiler benötigt das zusätzliche Material, um Typen abzuleiten.

Peter Schmitz
quelle
Da ich dies in meiner Antwort nicht wirklich hatte - der Grund, warum Sie Probleme haben, ist, dass die Typinferenz von links nach rechts am besten funktioniert, aber Sie binden Ihre Token aufgrund der Priorität des Operators von rechts nach links zusammen. Wenn Sie alle Ihre Aussagen mit Worten (mit derselben Priorität) versehen und die Art und Weise ändern, in der sich die Dinge gruppieren, erhalten Sie die gewünschte Schlussfolgerung. (Dh Sie müßten HasIs, IsWithCondition, ConditionAndTrueCaseKlassen , die Teile des Ausdrucks aufbauen würde von links nach rechts.)
Rex Kerr
Ich nahm unbewusst die Art der Typinferenz von links nach rechts an, blieb aber bei der Operatorpriorität und Assoziativität von Methodennamen, insbesondere beginnend mit ?vor jedem anderen Alphanum-Zeichen als Methodenname erstes Zeichen und a :für linke Assoziativität. Ich muss also neue Methodennamen überdenken, damit die Typinferenz von links nach rechts funktioniert. Vielen Dank!
Peter Schmitz

Antworten:

28

Wir können kombinieren Wie man einen ternären Operator in Scala definiert, der führende Token bewahrt? mit der Antwort auf Ist Option, die einen Wert umschließt, ein gutes Muster? bekommen

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

Ist das für Ihre Bedürfnisse angemessen?

Rex Kerr
quelle
Das kommt dem sehr nahe, was ich vorhabe. schöner Ansatz. Ich werde darüber nachdenken. Mein Grund, den allerersten Code zu vermeiden, bestand darin, präziser zu sein, weil ich valfür eine folgende ifAussage keine temporäre Aussage hatte: Machen Sie es in einer Zeile verständlich, so wie man es sich vorstellt.
Peter Schmitz
125

Aus Tony Morris 'Lambda-Blog :

Ich höre diese Frage oft. Ja tut es. Stattdessen c ? p : qwird geschrieben if(c) p else q.

Dies ist möglicherweise nicht vorzuziehen. Vielleicht möchten Sie es mit der gleichen Syntax wie Java schreiben. Das kannst du leider nicht. Dies liegt daran, dass :es sich nicht um eine gültige Kennung handelt. Fürchte dich nicht, |ist! Würden Sie sich damit zufrieden geben?

c ? p | q

Dann benötigen Sie den folgenden Code. Beachten Sie die =>Annotationen call-by-name ( ) zu den Argumenten. Diese Auswertungsstrategie ist erforderlich, um den ternären Operator von Java korrekt neu zu schreiben. Dies ist in Java selbst nicht möglich.

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

Hier ist ein Beispiel mit dem neuen Operator, den wir gerade definiert haben:

object T {   val condition = true

  import Bool._

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

Habe Spaß ;)

Landei
quelle
Ja, ich habe das schon einmal gesehen, aber der Unterschied ist, dass ich den (ausgewerteten) Wert meines ersten Ausdrucks als Argument in der thenund else-Klausel habe.
Peter Schmitz
5
Ich habe den if(c) p else qAnsatz gewählt ... das Fehlen von Zahnspangen macht mich ein wenig unangenehm, aber das ist nur eine
Stilsache
17

Rex Kerrs Antwort in Scala ausgedrückt:

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

obwohl ich nicht sicher bin, welchen Teil des if-else-Konstrukts Sie optimieren möchten.

Debilski
quelle
sehr gerader Weg. manchmal vergisst man die täglichen Match / Case-Aussagen. Ich habe mich nur an die einzeilige ternäre Sprache gehalten if then else, aber es ist in der Tat eine verständliche Art zu lösen.
Peter Schmitz
1
Pattern Matching lässt sich leicht auf mehr als zwei Zweige skalieren.
Raphael
0

Da: an sich kein gültiger Operator ist, es sei denn, Sie können ihn immer mit Back Ticks umgehen :, können Sie ein anderes Zeichen verwenden, z. B. "|" wie in einer der obigen Antworten. Aber wie wäre es mit Elvis mit einem Spitzbart? ::

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

Dies funktioniert natürlich wieder nicht, wenn Ihre Werte Listen sind, da sie selbst den Operator :: haben.

Ustaman Sangat
quelle