sentencia lenguaje funciona else ejemplos como scala for-comprehension

scala - lenguaje - ¿Cómo puedo hacer ''if..else'' dentro de una comprensión?



lenguaje c if else ejemplos (6)

Estoy haciendo una pregunta muy básica que me confundió recientemente. Quiero escribir una expresión de Scala For para hacer algo como lo siguiente:

for (i <- expr1) { if (i.method) { for (j <- i) { if (j.method) { doSomething() } else { doSomethingElseA() } } } else { doSomethingElseB() } }

El problema es que, en los múltiples generadores para expresión, no sé dónde puedo poner cada uno para expresión cuerpo.

for {i <- expr1 if(i.method) // where can I write the else logic ? j <- i if (j.method) } doSomething()

¿Cómo puedo reescribir el código en Scala Style?


El primer código que escribiste es perfectamente válido, por lo que no es necesario volver a escribirlo. En otro lugar dijiste que querías saber cómo hacerlo al estilo Scala. Realmente no hay un "estilo Scala", pero asumiré un estilo más funcional y lo abordaré.

for (i <- expr1) { if (i.method) { for (j <- i) { if (j.method) { doSomething() } else { doSomethingElseA() } } } else { doSomethingElseB() } }

La primera preocupación es que esto no devuelve ningún valor. Todo lo que hace son los efectos secundarios, que también deben evitarse. Entonces el primer cambio sería así:

val result = for (i <- expr1) yield { if (i.method) { for (j <- i) yield { if (j.method) { returnSomething() // etc

Ahora, hay una gran diferencia entre

for (i <- expr1; j <- i) yield ...

y

for (i <- expr1) yield for (j <- i) yield ...

Devuelven cosas diferentes, y hay momentos en que quieres lo último, no lo primero. Aunque asumiré que quieres lo primero. Ahora, antes de continuar, vamos a arreglar el código. Es feo, difícil de seguir y poco informativo. Vamos a refactorizarlo extrayendo métodos.

def resultOrB(j) = if (j.method) returnSomething else returnSomethingElseB def nonCResults(i) = for (j <- i) yield resultOrB(j) def resultOrC(i) = if (i.method) nonCResults(i) else returnSomethingC val result = for (i <- expr1) yield resultOrC(i)

Ya está mucho más limpio, pero no está devolviendo lo que esperamos. Veamos la diferencia:

trait Element object Unrecognized extends Element case class Letter(c: Char) extends Element case class Punct(c: Char) extends Element val expr1 = "This is a silly example." split "//b" def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j) def validElements(i: String) = for (j <- i) yield wordOrPunct(j) def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else Unrecognized val result = for (i <- expr1) yield classifyElements(i)

El tipo de result es Array[AnyRef] , mientras que el uso de múltiples generadores generaría un Array[Element] . La parte fácil de la solución es esta:

val result = for { i <- expr1 element <- classifyElements(i) } yield element

Pero eso solo no funcionará, porque AnyRef devuelve AnyRef , y queremos que devuelva una colección. Ahora, validElements devuelve una colección, por lo que no es un problema. Solo necesitamos arreglar la else parte. Como validElements está devolviendo un IndexedSeq , también lo devolveremos en la parte else . El resultado final es:

trait Element object Unrecognized extends Element case class Letter(c: Char) extends Element case class Punct(c: Char) extends Element val expr1 = "This is a silly example." split "//b" def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j) def validElements(i: String) = for (j <- i) yield wordOrPunct(j) def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else IndexedSeq(Unrecognized) val result = for { i <- expr1 element <- classifyElements(i) } yield element

Eso hace exactamente la misma combinación de bucles y condiciones que presentaste, pero es mucho más legible y fácil de cambiar.

Sobre el rendimiento

Creo que es importante tener en cuenta una cosa sobre el problema presentado. Vamos a simplificarlo:

for (i <- expr1) { for (j <- i) { doSomething } }

Ahora, eso se implementa con foreach (consulte here , u otras preguntas y respuestas similares). Eso significa que el código anterior hace exactamente lo mismo que este código:

for { i <- expr1 j <- i } doSomething

Exactamente lo mismo. Eso no es cierto en absoluto cuando uno está utilizando el yield . Las siguientes expresiones no producen el mismo resultado:

for (i <- expr1) yield for (j <- i) yield j for (i <- expr1; j <- i) yield j

El primer fragmento de código se implementará a través de dos llamadas de map , mientras que el segundo fragmento de flatMap utilizará un map flatMap y un map .

Por lo tanto, es solo en el contexto del yield que incluso tiene algún sentido preocuparse por el anidamiento for bucles o el uso de múltiples generadores. Y, de hecho, los generadores representan el hecho de que algo se está generando , lo cual es cierto solo para las verdaderas comprensiones (las que yield algo).


La parte

for (j <- i) { if (j.method) { doSomething(j) } else { doSomethingElse(j) } }

se puede reescribir como

for(j <- i; e = Either.cond(j.method, j, j)) { e.fold(doSomething _, doSomethingElse _) }

(por supuesto, puedes usar un rendimiento si tus métodos do ... devuelven algo)

Aquí no es tan útil, pero si tiene una estructura anidada más profunda, podría ...


Las condiciones especificadas en un Scala para la operación actúan para filtrar los elementos de los generadores. Los elementos que no satisfacen las condiciones se descartan y no se presentan al bloque de código / rendimiento.

Lo que esto significa es que si desea realizar operaciones alternativas basadas en una expresión condicional, la prueba debe aplazarse hasta el bloque de rendimiento / código.

También tenga en cuenta que la operación para la operación es relativamente costosa (actualmente), por lo que quizás sea más apropiado un enfoque iterativo más simple, tal vez algo como:

expr1 foreach {i => if (i.method) { i foreach {j => if (j.method) doSomething() else doSomethingElseA() } } else doSomethingElseB() }

Actualizar:

Si debe usar una para la comprensión y puede vivir con algunas restricciones, esto podría funcionar:

for (i <- expr1; j <- i) { if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB() }


No se puede. La construcción for (expr; if) simplemente filtra el elemento que debe manejarse en el bucle.


Si el orden no es importante para las llamadas a doSomething () y doSomethingElse (), puede reorganizar el código de esta manera.

val (tmp, no1) = expr1.partition(_.method) val (yes, no2) = tmp.partition(_.method) yes.foreach(doSomething()) no1.foreach(doSomethingElse()) no2.foreach(doSomethingElse())

Para responder a su pregunta original, creo que, para comprender, puede ser bastante bueno para casos de uso específicos, y su ejemplo no encaja bien.


import scalaz._; import Scalaz._ val lhs = (_ : List[X]) collect { case j if j.methodJ => doSomething(j) } val rhs = (_ : List[X]) map doSomethingElse lhs <-: (expr1 partition methodI) :-> rhs