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