您编写的第一个代码完全有效,因此无需重写。在其他地方,您说您想知道如何以 Scala 风格进行操作。没有真正的“Scala 风格”,但我会假设一种更实用的风格并加以解决。
for (i <- expr1) {
if (i.method) {
for (j <- i) {
if (j.method) {
doSomething()
} else {
doSomethingElseA()
}
}
} else {
doSomethingElseB()
}
}
第一个问题是 this 没有返回值。它所做的只是副作用,这也是要避免的。所以第一个变化是这样的:
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 实现的(请参阅here,或其他类似的问题和答案)。这意味着上面的代码和下面的代码完全一样:
for {
i <- expr1
j <- i
} doSomething
完全一样。当使用yield 时,这根本不是真的。以下表达式不会产生相同的结果:
for (i <- expr1) yield for (j <- i) yield j
for (i <- expr1; j <- i) yield j
第一个 sn-p 将通过两个 map 调用实现,而第二个 sn-p 将使用一个 flatMap 和一个 map。
因此,只有在yield 的上下文中,才需要担心嵌套for 循环或使用多个生成器。而且,事实上,generators 代表某物正在生成这一事实,这仅适用于真正的理解(那些yielding 的东西)。