Ответ 1
Первый код, который вы написали, абсолютно корректен, поэтому нет необходимости переписывать его. В другом месте вы сказали, что хотите знать, как это сделать. Scala -стиль. На самом деле нет стиля Scala, но я буду использовать более функциональный стиль и придерживаться этого.
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
что-то).