Opérateur ternaire similaire à?:

94

J'essaie d'éviter les constructions comme celle-ci:

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

Ok, dans cet exemple, les branches thenet elsesont simples, mais vous pouvez créer des images complexes. J'ai construit ce qui suit:

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

Défini cela, je peux remplacer l'exemple simple ci-dessus par:

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

Mais comment puis-je me débarrasser du s: String =>? Je veux quelque chose comme ça:

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

Je suppose que le compilateur a besoin des éléments supplémentaires pour déduire les types.

Peter Schmitz
la source
Comme je n'avais pas vraiment cela dans ma réponse - la raison pour laquelle vous rencontrez des problèmes est que l'inférence de type fonctionne mieux de gauche à droite, mais vous liez vos jetons de droite à gauche en raison de la priorité des opérateurs. Si vous faites toutes vos déclarations des mots (avec la même priorité) et changez la façon dont les choses se regroupent, vous obtiendrez l'inférence que vous voulez. (Vous auriez HasIs, IsWithCondition, des ConditionAndTrueCaseclasses qui accumulerait parties de l'expression de gauche à droite.)
Rex Kerr
J'ai inconsciemment présumé la voie de l'inférence de type de gauche à droite, mais collé à la priorité des opérateurs et à l'associativité des noms de méthodes, en particulier en commençant par ?avant tout autre caractère alphanum comme nom de méthode, premier caractère et un :pour l'associativité gauche. Je dois donc repenser les nouveaux noms de méthodes pour que l'inférence de type fonctionne de gauche à droite. Merci!
Peter Schmitz

Réponses:

28

On peut combiner Comment définir un opérateur ternaire dans Scala qui préserve les tokens principaux? avec la réponse à Option enveloppant une valeur un bon modèle? obtenir

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

Cela répond-il à vos besoins?

Rex Kerr
la source
C'est très proche de ce que j'ai en tête. belle approche. J'y penserai. Ma raison d'éviter le tout premier code était d'être plus concis en n'ayant pas de déclaration temporaire valpour une ifdéclaration suivante : faites-le intelligible en une seule ligne, comme on l'a à l'esprit.
Peter Schmitz
125

Du blog Lambda de Tony Morris :

J'entends beaucoup cette question. Oui. Au lieu de c ? p : q, il est écrit if(c) p else q.

Cela peut ne pas être préférable. Vous aimeriez peut-être l'écrire en utilisant la même syntaxe que Java. Malheureusement, vous ne pouvez pas. C'est parce que ce :n'est pas un identifiant valide. N'ayez pas peur, |c'est! Accepteriez-vous cela?

c ? p | q

Ensuite, vous aurez besoin du code suivant. Notez les =>annotations call-by-name ( ) sur les arguments. Cette stratégie d'évaluation est nécessaire pour réécrire correctement l'opérateur ternaire de Java. Cela ne peut pas être fait en Java lui-même.

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

Voici un exemple utilisant le nouvel opérateur que nous venons de définir:

object T {   val condition = true

  import Bool._

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

S'amuser ;)

Landei
la source
oui, j'ai déjà vu cela, mais la différence est que a j'ai la valeur (évaluée) de ma première expression comme argument dans la clause thenand else.
Peter Schmitz
5
J'ai adopté l' if(c) p else qapproche ... le manque d'accolades me met un peu mal à l'aise mais c'est juste une chose de style
rjohnston
17

Réponse de Rex Kerr exprimée en Scala de base:

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

même si je ne suis pas sûr de la partie de la construction if – else que vous souhaitez optimiser.

Debilski
la source
façon très droite. on oublie parfois les instructions match / case d'usage quotidien. Je suis juste resté fidèle à l' if then elseidiome ternaire d'une seule ligne , mais c'est en effet une manière intelligible de résoudre.
Peter Schmitz
1
Pattern Matching s'adapte facilement à plus de deux branches.
Raphael
0

Puisque: en lui-même ne sera pas un opérateur valide à moins que vous ne vouliez toujours l'échapper avec des graduations inversées :, vous pouvez utiliser un autre caractère, par exemple "|" comme dans l'une des réponses ci-dessus. Mais qu'en est-il d'Elvis avec une barbiche? ::

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

Bien sûr, cela ne fonctionnera pas encore si vos valeurs sont des listes, puisqu'elles ont elles-mêmes :: opérateur.

Ustaman Sangat
la source