Frage

Ich bin eine sehr grundlegende Frage zu stellen, die mir vor kurzem verwirrt. Ich mag eine Scala schreiben Für die Expression so etwas wie die folgenden tun:

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

Das Problem ist, dass in den mehrere Generatoren Für die Expression, ich weiß nicht, wo ich jeweils in Aufmachungen für den Ausdruck Körper.

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

Wie kann ich den Code in Scala Stil umschreiben?

War es hilfreich?

Lösung

Der erste Code, den Sie geschrieben hat, ist völlig in Ordnung, so gibt es keine Notwendigkeit, es neu zu schreiben. An anderer Stelle sagen, Sie wissen wollen, wie es Scala-Stil zu tun. Es ist nicht wirklich ein „Scala-Stil“, aber ich werde einen funktionalen Stil und tack, dass nehmen.

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

Die erste Sorge ist, dass diese keinen Wert zurückgibt. Denn es macht nicht sind Nebenwirkungen, die auch vermieden werden sollen. So ist die erste Änderung so sein würde:

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

Jetzt gibt es einen großen Unterschied zwischen

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

und

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

Sie kehren verschiedene Dinge, und es gibt Zeiten, die Sie die später wollen, nicht das erstere. Ich nehme an, Sie das ehemalige wollen, though. Nun, bevor wir fortfahren, wollen wir beheben Sie den Code. Es ist hässlich, schwer zu folgen und wenig informativ. Lassen Sie uns refactor es durch Verfahren extrahiert werden.

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)

Es ist schon viel sauberer, aber es kehrt nicht ganz das, was wir erwarten. Schauen wir uns den Unterschied:

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)

Die Art der result dort Array[AnyRef] ist, während mehrere Generatoren mit Array[Element] ergeben würde. Der einfache Teil des Updates ist dies:

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

Aber das allein wird nicht funktionieren, weil classifyElements selbst zurück AnyRef, und wir wollen es eine Sammlung zurück. Nun validElements eine Sammlung zurückkehren, so dass kein Problem ist. Wir brauchen nur den else Teil zu beheben. Da validElements eine IndexedSeq zurückkehrt, kehren wir, dass auch auf dem else Teil. Das Endergebnis ist:

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

Das tut genau die gleiche Kombination von Schleifen und Bedingungen, wie Sie präsentierte, aber es ist viel besser lesbar und leicht zu ändern.

Über Yield

Ich denke, es ist wichtig, eine Sache, über das Problem präsentiert zu beachten. Lassen Sie uns vereinfachen es:

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

Nun, das mit foreach (siehe hier implementiert ist, oder andere ähnliche Fragen und antworte). Das heißt, der obige Code tut genau das Gleiche wie diesem Code:

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

Genau das Gleiche. Das ist gar nicht wahr, wenn man yield verwenden. Die folgenden Ausdrücke ergeben nicht das gleiche Ergebnis:

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

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

Der erste Snippet wird durch zwei map Anrufe durchgeführt werden, während der zweite Schnipsel eine flatMap und eine map verwenden wird.

So ist es nur im Zusammenhang mit dem yield, dass es auch keinen Sinn zu Sorgen macht über for Schleifen nisten oder mehrere Generatoren. Und in der Tat, Generatoren steht für die Tatsache, dass etwas wird erzeugt , die für-Comprehensions nur gilt für wahr ist (die, die yielding etwas).

Andere Tipps

Der Teil

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

kann wie folgt umgeschrieben werden

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

(natürlich kann man eine Ausbeute verwenden kann stattdessen, wenn Ihre do .. Methoden etwas zurück)

Hier ist es nicht so schrecklich nützlich, aber wenn Sie eine tiefer verschachtelte Struktur, es könnte ...

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

Sie können nicht. Die für (expr; if). Konstrukt nur das Filterelement, das in der Schleife behandelt werden muß

Wenn der Auftrag für die Anrufe zu doSomething nicht wichtig ist () und doSomethingElse (), dann können Sie den Code wie folgt neu anordnen.

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

yes.foreach(doSomething())
no1.foreach(doSomethingElse())
no2.foreach(doSomethingElse())

Ihre ursprüngliche Frage zu beantworten, ich denke, dass für Comprehensions für spezifische Anwendungsfälle ganz nett sein können, und Ihr Beispiel nicht gut paßt.

Die in einem Scala festgelegten Bedingungen für den Betrieb wirkt die Elemente von den Generatoren zu filtern. Elemente, die nicht die Bedingungen erfüllen, werden verworfen und sind nicht auf die Ausbeute / Codeblock dargestellt.

Das bedeutet, dass, wenn Sie auf einem bedingten Ausdruck basiert alternative Operationen durchführen möchten, die Testanforderungen der Ausbeute / Codeblock verschoben werden.

Beachten Sie auch, dass die für den Betrieb zu berechnen relativ teuer ist (derzeit) so vielleicht ein einfacher iterativer Ansatz besser geeignet sein könnte, vielleicht so etwas wie:

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

Update:

Wenn Sie Verständnis verwenden ein Muss, und Sie können mit einigen Einschränkungen leben, könnte dies Arbeit:

for (i <- expr1; j <- i) {
  if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top