我问一个非常基本的问题,最近让我很困惑。我想编写一个 Scala For 表达式来执行如下操作:

for (i <- expr1) {
  if (i.method) {
    for (j <- i) {
      if (j.method) {
        doSomething()
      } else {
        doSomethingElseA()
      }
    }
  } else {
    doSomethingElseB()
  }
}

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

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

如何以 Scala 风格重写代码?

有帮助吗?

解决方案

您编写的第一个代码是完全有效的,因此无需重写它。在其他地方,您说您想知道如何做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)

类型 resultArray[AnyRef], ,同时使用多个发电机会产生 Array[Element]. 。修复程序的简单部分是:

val result = for {
  i <- expr1
  element <- classifyElements(i)
} yield element

但是,仅此一项就无法正常工作,因为分类元素本身会返回 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一些东西)。

其他提示

那个部分

for (j <- i) {
   if (j.method) {
     doSomething(j)
   } else {
     doSomethingElse(j)
   }
 }

可以重写为

for(j <- i; e = Either.cond(j.method, j, j)) {
  e.fold(doSomething _, doSomethingElse _)  
}  

(当然,如果您这样做,您可以使用产量。方法返回某物)

在这里,它并不是那么可怕,但是如果您具有更深的嵌套结构,则可以...

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

你不能。 for(expr;如果)构造只需过滤循环中必须处理的元素即可。

如果顺序对于调用 doSomething() 和 doSomethingElse() 并不重要,那么您可以像这样重新排列代码。

val (tmp, no1) = expr1.partition(_.method)
val (yes, no2) = tmp.partition(_.method)

yes.foreach(doSomething())
no1.foreach(doSomethingElse())
no2.foreach(doSomethingElse())

为了回答您原来的问题,我认为对于特定用例来说,理解可能非常好,但您的示例不太适合。

Scala在操作ACT中指定的条件以过滤发生器的元素。不满足条件的元素被丢弃,未呈现给收益 /代码块。

这意味着,如果您想根据条件表达式执行替代操作,则需要将测试推迟到收益率 /代码块。

还要注意,操作的计算相对昂贵(当前),因此也许更简单的迭代方法可能更合适,也许是类似的东西:

expr1 foreach {i =>
  if (i.method) {
    i foreach {j =>
      if (j.method)
        doSomething()
      else
        doSomethingElseA()
    }
  }
  else
    doSomethingElseB()
}

更新:

如果您必须使用A进行理解,并且可以受到一些限制,则可能有效:

for (i <- expr1; j <- i) {
  if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top