我该如何理解? [英] How can I do 'if..else' inside a for-comprehension?

查看:90
本文介绍了我该如何理解?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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:

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 {i <- expr1
  if(i.method) // where can I write the else logic ?
  j <- i 
  if (j.method)
} doSomething()

如何用Scala风格重写代码?

How can I rewrite the code in Scala Style?

推荐答案

您编写的第一个代码是完全有效的,因此无需重写.在其他地方,您说过您想知道如何做到Scala风格.并没有真正的"Scala风格",但我将假定一种更具实用性的风格并加以补充.

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.

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 ...

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)

result的类型为Array[AnyRef],而使用多个生成器将产生Array[Element].该修补程序最简单的部分是:

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

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

但是,仅凭这点是行不通的,因为classifyElements本身会返回AnyRef,我们希望它返回集合.现在,validElements返回一个集合,所以这不是问题.我们只需要修复else部分.由于validElements返回的是IndexedSeq,因此我们也应在else部分返回它.最终结果是:

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

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.

关于收益

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

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

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

现在,可以通过foreach实现(请参见此处或其他类似的问题和答案).这意味着上面的代码与该代码具有完全相同的作用:

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:

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

完全一样.当人们使用yield时,这根本不正确.以下表达式不会产生相同的结果:

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

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

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

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

The first snippet will be implemented through two map calls, while the second snippet will use one flatMap and one map.

因此,只有在yield的上下文中,担心嵌套for循环或使用多个生成器才有意义.而且,实际上,生成器表示某事物正在被生成的事实,这仅对于真正的理解(yield某事物的理解)是正确的.

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

这篇关于我该如何理解?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆