scala 我怎样才能在 for-comprehension 中做“if..else”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4192227/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 02:33:55  来源:igfitidea点击:

How can I do 'if..else' inside a for-comprehension?

scalafor-comprehension

提问by Sawyer

I am asking a very basic question which confused me recently. I want to write a Scala For expression to do something like the following:

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

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

The problem is that, in the multiple generators For expression, I don't know where can I put each for expression body.

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

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

How can I rewrite the code in Scala Style?

如何以 Scala 风格重写代码?

采纳答案by Daniel C. Sobral

The first code you wrote is perfectly valid, so there's no need to rewrite it. Elsewhere you said you wanted to know how to do it Scala-style. There isn't really a "Scala-style", but I'll assume a more functional style and tack that.

您编写的第一个代码是完全有效的,因此无需重写。在其他地方,您说您想知道如何以 Scala 风格进行操作。没有真正的“Scala 风格”,但我会假设一个更实用的风格并加以解决。

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

The first concern is that this returns no value. All it does is side effects, which are to be avoided as well. So the first change would be like this:

第一个问题是这不返回任何值。它所做的只是副作用,这也是要避免的。所以第一个变化是这样的:

val result = for (i <- expr1) yield {
  if (i.method) {
    for (j <- i) yield {
      if (j.method) {
        returnSomething()
        // etc

Now, there's a big difference between

现在,两者之间有很大的区别

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

and

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

They return different things, and there are times you want the later, not the former. I'll assume you want the former, though. Now, before we proceed, let's fix the code. It is ugly, difficult to follow and uninformative. Let's refactor it by extracting methods.

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

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)

It is already much cleaner, but it isn't returning quite what we expect. Let's look at the difference:

它已经干净多了,但它并没有像我们期望的那样返回。我们来看看区别:

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)

The type of resultthere is Array[AnyRef], while using multiple generators would yield Array[Element]. The easy part of the fix is this:

resultthere is的类型Array[AnyRef],而使用多个生成器会产生Array[Element]. 修复的简单部分是这样的:

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

But that alone won't work, because classifyElements itself returns AnyRef, and we want it returning a collection. Now, validElementsreturn a collection, so that is not a problem. We only need to fix the elsepart. Since validElementsis returning an IndexedSeq, let's return that on the elsepart as well. The final result is:

但这本身是行不通的,因为分类元素本身返回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

That does exactly the same combination of loops and conditions as you presented, but it is much more readable and easy to change.

这与您所展示的循环和条件的组合完全相同,但它更具可读性且易于更改。

About Yield

关于收益

I think it is important to note one thing about the problem presented. Let's simplify it:

我认为重要的是要注意所提出的问题的一件事。让我们简化一下:

for (i <- expr1) {
  for (j <- i) {
    doSomething
  }
}

Now, that is implemented with foreach(see here, or other similar questions and answer). That means the code above does exactly the same thing as this code:

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

for {
  i <- expr1
  j <- i
} doSomething

Exactly the same thing. That is not true at all when one is using yield. The following expressions do not yield the same result:

完全一样的东西。当人们使用yield. 以下表达式不会产生相同的结果:

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

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

The first snippet will be implemented through two mapcalls, while the second snippet will use one flatMapand one map.

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

So, it is only in the context of yieldthat it even makes any sense to worry about nesting forloops or using multiple generators. And, in fact, generatorsstands for the fact that something is being generated, which is only true of true for-comprehensions (the ones yielding something).

因此,只有在上下文中yield,担心嵌套for循环或使用多个生成器才有意义。而且,事实上,generators代表正在生成某物的事实,这仅适用于真正的 for-comprehensions(那些yielding 某物)。

回答by Landei

The part

那个部分

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

can be rewritten as

可以改写为

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

(of course you can use a yield instead if your do.. methods return something)

(当然,如果你的方法返回一些东西,你可以使用 yield 代替)

Here it is not so terrible useful, but if you have a deeper nested structure, it could...

在这里它不是那么有用,但如果你有一个更深的嵌套结构,它可能......

回答by oxbow_lakes

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

回答by Nicolas

You can not. The for(expr; if) construct just filter the element that must be handled in the loop.

你不能。for(expr; if) 构造只是过滤必须在循环中处理的元素。

回答by Craig P. Motlin

If the order isn't important for the calls to doSomething() and doSomethingElse() then you can rearrange the code like this.

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

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

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

To answer your original question, I think that for comprehensions can be quite nice for specific use cases, and your example doesn't fit nicely.

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

回答by Don Mackenzie

The conditions specified in a Scala for operation act to filter the elements from the generators. Elements not satisfying the conditions are discarded and are not presented to the yield / code block.

Scala 中为操作指定的条件用于从生成器中过滤元素。不满足条件的元素将被丢弃并且不会呈现给产量/代码块。

What this means is that if you want to perform alternate operations based on a conditional expression, the test needs to be deferred to the yield / code block.

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

Also be aware that the for operation is relatively expensive to compute (currently) so perhaps a simpler iterative approach might be more appropriate, perhaps something like:

另请注意, for 操作的计算成本(当前)相对较高,因此可能更简单的迭代方法可能更合适,例如:

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

Update:

更新:

If you must use a for comprehension and you can live with some restrictions, this might work:

如果您必须使用 a 来理解并且可以忍受一些限制,这可能会奏效:

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