2011-02-09 33 views
73

que estoy tratando de evitar las construcciones de esta manera:ternario Operador similar a:?

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

Ok, en este ejemplo el then y else rama son simples, pero se puede los complejos de imagen. que construyeron los siguientes:

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 que, puedo reemplazar el sencillo ejemplo anterior con:

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

Pero, ¿cómo puedo deshacerme de la s: String =>? Quiero algo así:

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

Supongo que el compilador necesita el material extra para inferir los tipos.

+0

Desde que en realidad no tienen esto en mi respuesta - la razón por la que' Si tienes problemas es que la inferencia de tipo funciona mejor de izquierda a derecha, pero estás uniendo tus tokens de derecha a izquierda debido a la precedencia del operador. Si redactas todas las palabras de tus declaraciones (con la misma precedencia) y cambias la forma en que las cosas se agrupan, obtendrás la inferencia que deseas. (Es decir, tendría las clases 'HasIs',' IsWithCondition', 'ConditionAndTrueCase' que crearían partes de la expresión de izquierda a derecha.) –

+0

Me imaginé inconscientemente el camino de la inferencia de tipo de izquierda a derecha, pero trabada con la precedencia del operador y asociatividad de nombres de métodos, especialmente comenzando con '?' antes que cualquier otro carácter alfanumérico como nombre de método primero char y '' 'para asociatividad izquierda. Así que tengo que replantearme los nuevos nombres de métodos para que la inferencia de tipos funcione de izquierda a derecha. ¡Gracias! –

Respuesta

20

podemos combinar How to define a ternary operator in Scala which preserves leading tokens? con la respuesta a Is Option wrapping a value a good pattern? para obtener

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?

+0

Eso es muy cercano a lo que tengo en mente. buen enfoque. Pensaré sobre eso. Mi razón para evitar el primer código era ser más conciso al no tener un 'val' temporal para una siguiente declaración' if': Hacerlo inteligible en una línea, como uno tenerlo en mente. –

13

Rex Kerr’s answer expresa 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 si-else que desea optimizar.

+0

manera muy recta. a veces uno se olvida de las declaraciones de coincidencia/caso de uso diario. Solo me quedé con el idioma ternario de "si es que no", pero de hecho es una forma inteligible de resolverlo. –

+0

La coincidencia de patrones se adapta fácilmente a más de dos ramas. – Raphael

100

De Tony Morris' Lambda Blog:

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

Esto puede no ser preferible. Tal vez le gustaría escribir usando la misma sintaxis que Java. Tristemente, no puedes. Esto es porque : no es un identificador válido. No temas, | es! ¿Te conformarías con esto?

c ? p | q 

Luego necesitará 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" 
} 

Diviértete;)

+0

sí, he visto esto antes, pero la diferencia es que a tengo el valor (evaluado) de mi primera expresión como argumento en la cláusula 'then' y' else'. –

+0

@Imre: Corregí el enlace y copié el contenido. – Landei

+2

Tomé el enfoque 'if (c) p else q' ... la falta de llaves me hace sentir un poco incómodo, pero eso es solo una cuestión de estilo – rjohnston

0

desde: por sí sola no será un operador válida a menos que esté bien con siempre escapándolo con retrocesos :, podría ir con otro personaje, por ejemplo "|" como en una de las respuestas anteriores. Pero ¿qué hay de Elvis con una 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 de nuevo no funcionará si los valores son listas, ya que tienen :: operador de sí mismos.

Cuestiones relacionadas