Operador ternario similar a?:

94

Estoy tratando de evitar construcciones como esta:

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

Ok, en este ejemplo, la rama theny elsees simple, pero puedes crear imágenes complejas. Construí lo siguiente:

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

Definido eso, puedo reemplazar el ejemplo simple anterior con:

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

Pero, ¿cómo puedo deshacerme del s: String =>? Quiero algo como eso:

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

Supongo que el compilador necesita material adicional para inferir tipos.

Peter Schmitz
fuente
Dado que en realidad no tenía esto en mi respuesta, la razón por la que tiene problemas es que la inferencia de tipos funciona mejor de izquierda a derecha, pero está vinculando sus tokens de derecha a izquierda debido a la precedencia del operador. Si convierte todas sus declaraciones en palabras (con la misma precedencia) y cambia la forma en que las cosas se agrupan, obtendrá la inferencia que desea. (Es decir, que tendría HasIs, IsWithCondition, ConditionAndTrueCaseclases que se acumularían partes de la expresión de izquierda a derecha.)
Rex Kerr
Inconscientemente supuse la forma de inferencia de tipos de izquierda a derecha, pero atascado con la precedencia de los operadores y la asociatividad de los nombres de los métodos, especialmente comenzando ?antes de cualquier otro carácter alfanumérico como nombre de método first char y a :para asociatividad izquierda. Así que tengo que repensar los nombres de los nuevos métodos para que la inferencia de tipos funcione de izquierda a derecha. ¡Gracias!
Peter Schmitz

Respuestas:

28

Podemos combinar ¿Cómo definir un operador ternario en Scala que conserva los tokens iniciales? con la respuesta a ¿Opción envolver un valor es un buen patrón? Llegar

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

¿Es esto adecuado para sus necesidades?

Rex Kerr
fuente
Eso es muy parecido a lo que tengo en mente. buen enfoque. Pensaré sobre eso. Mi razón para evitar el primer código fue para ser más conciso al no tener un temporal valpara una siguiente ifdeclaración: hágalo inteligible en una línea, tal como uno lo tiene en mente.
Peter Schmitz
125

Del blog Lambda de Tony Morris :

Escucho mucho esta pregunta. Si lo hace. En lugar de c ? p : q, está escrito if(c) p else q.

Puede que esto no sea preferible. Quizás le gustaría escribirlo usando la misma sintaxis que Java. Lamentablemente, no puedes. Esto se debe a :que no es un identificador válido. ¡No temas, |es! ¿Te conformarías con esto?

c ? p | q

Entonces necesitarás el siguiente código. Observe las =>anotaciones de llamada por nombre ( ) en los argumentos. Esta estrategia de evaluación es necesaria para reescribir correctamente el operador ternario de Java. Esto no se puede hacer en Java.

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

Aquí hay un ejemplo usando el nuevo operador que acabamos de definir:

object T {   val condition = true

  import Bool._

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

Que te diviertas ;)

Landei
fuente
sí, he visto esto antes, pero la diferencia es que tengo el valor (evaluado) de mi primera expresión como argumento en la cláusula theny else.
Peter Schmitz
5
Tomé el if(c) p else qenfoque ... la falta de apoyos que me hace un toque incómodo pero eso es sólo una cosa de estilo
rjohnston
17

La respuesta de Rex Kerr expresada en Scala básica:

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

aunque no estoy seguro de qué parte de la construcción if-else desea optimizar.

Debilski
fuente
camino muy recto. a veces uno se olvida de las declaraciones de casos y coincidencias de uso diario. Me limité al if then elselenguaje ternario de una línea , pero de hecho es una forma inteligible de resolver.
Peter Schmitz
1
Pattern Matching escala fácilmente a más de dos ramas.
Raphael
0

Dado que: por sí solo no será un operador válido a menos que esté de acuerdo con escapar siempre con tics inversos :, puede ir con otro carácter, por ejemplo, "|" como en una de las respuestas anteriores. ¿Pero qué tal Elvis con perilla? ::

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

Por supuesto, esto nuevamente no funcionará si sus valores son listas, ya que tienen :: operador ellos mismos.

Ustaman Sangat
fuente