Wie kann ich tun ‚if..else‘ in einem für Verständnis?
-
10-10-2019 - |
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?
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 yield
ing 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()
}