Первый код, который вы написали, абсолютно корректен, поэтому его не нужно переписывать.В другом месте вы сказали, что хотите знать, как это сделать в стиле Scala.На самом деле «Scala-style» не существует, но я возьму более функциональный стиль и остановлюсь на этом.
for (i <- expr1) {
if (i.method) {
for (j <- i) {
if (j.method) {
doSomething()
} else {
doSomethingElseA()
}
}
} else {
doSomethingElseB()
}
}
Первая проблема заключается в том, что это не возвращает никакого значения.Все, что он делает, - это побочные эффекты, которых также следует избегать.Итак, первое изменение будет следующим:
val result = for (i <- expr1) yield {
if (i.method) {
for (j <- i) yield {
if (j.method) {
returnSomething()
// etc
Теперь есть большая разница между
for (i <- expr1; j <- i) yield ...
и
for (i <- expr1) yield for (j <- i) yield ...
Они возвращают разные вещи, ибывают времена, когда вы хотите позже, а не первое.Я предполагаю, что вы хотите первое, хотя.Теперь, прежде чем мы продолжим, давайте исправим код.Это ужасно, трудно следовать и неинформативно.Давайте сделаем рефакторинг, извлекая методы.
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)
Это уже намного чище, но не дает того, что мы ожидаем.Давайте посмотрим на разницу:
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)
Тип result
существует Array[AnyRef]
, тогда как использование нескольких генераторов даст Array[Element]
.Простая часть исправления заключается в следующем:
val result = for {
i <- expr1
element <- classifyElements(i)
} yield element
Но это само по себе не сработает, потому что сама classifyElements возвращает AnyRef
, и мы хотим, чтобы она возвращала коллекцию.Теперь validElements
возвращает коллекцию, так что это не проблема.Нам нужно только исправить часть else
.Так как validElements
возвращает IndexedSeq
, давайте вернем его также и в else
.Итоговый результат:
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
Это точно такая же комбинация циклов и условий, как вы представили, но она гораздо более читабельна и ее легко изменить.
О доходности
Я думаю, что важно отметить одну вещь о представленной проблеме.Давайте упростим это:
for (i <- expr1) {
for (j <- i) {
doSomething
}
}
Теперь это реализовано с помощью foreach
(см. здесь или другие подобные вопросы и ответы).Это означает, что приведенный выше код делает то же самое, что и этот код:
for {
i <- expr1
j <- i
} doSomething
Точно так же.Это совсем не так, когда вы используете yield
.Следующие выражения не дают одинакового результата:
for (i <- expr1) yield for (j <- i) yield j
for (i <- expr1; j <- i) yield j
Первый фрагмент будет реализован с помощью двух вызовов map
, тогда как второй фрагмент будет использовать один flatMap
и один map
.
Таким образом, только в контексте yield
имеет смысл беспокоиться о вложении циклов for
или использовании нескольких генераторов.И, на самом деле, огенераторы означают тот факт, что что-то генерируется генерируется , что верно только для истинных понятий для понимания (тех, которые yield
что-то).