我怎样才能在for-comprehension中做'if..else'?

Saw*_*yer 18 scala for-comprehension

我问的是一个让我最近困惑的基本问题.我想编写一个Scala For表达式来执行以下操作:

for (i <- expr1) {
  if (i.method) {
    for (j <- i) {
      if (j.method) {
        doSomething()
      } else {
        doSomethingElseA()
      }
    }
  } else {
    doSomethingElseB()
  }
}
Run Code Online (Sandbox Code Playgroud)

问题是,在多个生成器For表达式中,我不知道我可以在哪里放置表达式主体.

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

如何在Scala Style中重写代码?

Dan*_*ral 19

您编写的第一个代码完全有效,因此无需重写它.在其他地方,你说你想知道如何做Scala风格.没有真正的"Scala风格",但我会假设一种更具功能性的风格.

for (i <- expr1) {
  if (i.method) {
    for (j <- i) {
      if (j.method) {
        doSomething()
      } else {
        doSomethingElseA()
      }
    }
  } else {
    doSomethingElseB()
  }
}
Run Code Online (Sandbox Code Playgroud)

第一个问题是这不会返回任何值.它所做的就是副作用,这也是要避免的.所以第一次改变是这样的:

val result = for (i <- expr1) yield {
  if (i.method) {
    for (j <- i) yield {
      if (j.method) {
        returnSomething()
        // etc
Run Code Online (Sandbox Code Playgroud)

现在,两者之间存在很大差异

for (i <- expr1; j <- i) yield ...
Run Code Online (Sandbox Code Playgroud)

for (i <- expr1) yield for (j <- i) yield ...
Run Code Online (Sandbox Code Playgroud)

他们返回不同的东西,有时你想要的是后者,而不是前者.不过,我会假设你想要前者.现在,在我们继续之前,让我们修复代码.这是丑陋的,难以遵循和没有信息.让我们通过提取方法来重构它.

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)
Run Code Online (Sandbox Code Playgroud)

它已经更加清洁,但它并没有像我们期望的那样回归.让我们来看看差异:

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)
Run Code Online (Sandbox Code Playgroud)

result有的类型Array[AnyRef],虽然使用多个发电机将产生Array[Element].修复的简单部分是这样的:

val result = for {
  i <- expr1
  element <- classifyElements(i)
} yield element
Run Code Online (Sandbox Code Playgroud)

但仅此一点是行不通的,因为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
Run Code Online (Sandbox Code Playgroud)

这与您提供的循环和条件完全相同,但它更易读,更容易更改.

关于收益率

我认为重要的是要注意所提出的问题.让我们简化它:

for (i <- expr1) {
  for (j <- i) {
    doSomething
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,这是实现的foreach(见这里,或其他类似的问题和答案).这意味着上面的代码与此代码完全相同:

for {
  i <- expr1
  j <- i
} doSomething
Run Code Online (Sandbox Code Playgroud)

完全一样的事情.当一个人使用时,这根本不是真的yield.以下表达式不会产生相同的结果:

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

for (i <- expr1; j <- i) yield j
Run Code Online (Sandbox Code Playgroud)

第一个代码段将通过两个map调用实现,而第二个代码段将使用一个flatMap和一个map.

因此,只有在这样的情况下yield,担心嵌套for循环或使用多个生成器才有意义.而且,事实上,生成器代表着正在生成某些事物的事实,这只适用于真正的理解(yield某些东西).