Pregunta

Me estoy haciendo una pregunta muy básica, que me confundió recientemente. Quiero escribir un Scala Para la expresión a 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 la expresión, no sé donde puedo poner a cada cuerpo de expresión.

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

¿Cómo puedo volver a escribir el código en Scala estilo?

¿Fue útil?

Solución

El primer código que escribió es perfectamente válido, así que no hay necesidad de volver a escribir. En otra parte dijo que quería saber cómo hacerlo Scala-estilo. No es realmente un "estilo Scala", pero voy a asumir un estilo más funcional y la pegajosidad eso.

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 nada. Todo lo que hace es efectos secundarios, que deben ser evitados también. Así que el primer cambio sería la siguiente:

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 ...

Vuelven las cosas diferentes, y hay veces que desea que la tarde, no el primero. Vamos a suponer que desea que el primero, sin embargo. Ahora, antes de continuar, vamos a arreglar el código. Es feo, difícil de seguir y no informativo. refactor Vamos hacerle frente mediante la extracción de los 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 es mucho más limpio, pero no está volviendo bastante lo que esperamos. Echemos un vistazo a 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 hay Array[AnyRef], mientras que el uso de múltiples generadores produciría Array[Element]. La parte fácil de la solución es la siguiente:

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

Pero eso solo no va a funcionar, porque classifyElements mismo vuelve AnyRef, y queremos que el retorno de una colección. Ahora, validElements devolver una colección, por lo que no es un problema. Tan sólo hay que fijar la parte else. Desde validElements devuelve un IndexedSeq, volvamos que de parte else también. 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 usted presentó, pero es mucho más fácil de leer y fácil de cambiar.

Acerca de Rendimiento

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

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

Ahora, que se implementa con foreach (ver aquí , u otras preguntas similares y la respuesta). 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á usando yield. Las siguientes expresiones no dan el mismo resultado:

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

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

El primer fragmento se llevará a cabo a través de dos llamadas map, mientras que el segundo fragmento utilizará uno flatMap y uno map.

Por lo tanto, es sólo en el contexto de yield que incluso tiene sentido que preocuparse de anidación bucles for o el uso de múltiples generadores. Y, de hecho, generadores es el hecho de que algo está siendo genera , que sólo es cierto en cierto para-comprensiones (los yielding algo).

Otros consejos

La parte

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

puede ser reescrito como

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

(por supuesto se puede usar un rendimiento en su lugar si su hacer .. métodos devuelven algo)

En este caso no es tan terrible útil, pero si usted tiene una estructura anidada, se podría ...

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

No se puede. El para (expr; si). Constructo simplemente a filtrar el elemento que se debe manipular en el bucle

Si la orden no es importante para las llamadas a doSomething () y doSomethingElse (), entonces usted puede cambiar el código como este.

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 por comprensiones pueden ser bastante agradable para los casos de uso específicos, y su ejemplo no se ajusta muy bien.

Las condiciones especificadas en un Scala para la operación acto para filtrar los elementos de los generadores. Elementos que no satisfagan las condiciones se descartan y no se presentan al bloque de rendimiento / código.

Lo que esto significa es que si desea realizar operaciones alternativas en función de una expresión condicional, las necesidades de prueba para ser diferidos al bloque de rendimiento / código.

También tenga en cuenta que el requerido para la operación relativamente caros de cálculo (en la actualidad), de manera tal vez un enfoque iterativo más simple puede ser más apropiado, tal vez algo como:

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

Actualización:

Si tiene que usar una para la comprensión y se puede vivir con algunas restricciones, este trabajo puede:

for (i <- expr1; j <- i) {
  if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top