我该怎么办“if..else”在 for 理解中?

发布于 2024-10-02 20:38:49 字数 507 浏览 3 评论 0原文

我问一个非常基本的问题,最近让我很困惑。 我想编写一个 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 风格重写代码?

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()

How can I rewrite the code in Scala Style?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

苍暮颜 2024-10-09 20:38:49

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

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 实现的(请参阅此处< /a>,或其他类似的问题和答案)。这意味着上面的代码与此代码执行完全相同的操作:

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 循环或使用多个生成器才有意义。而且,事实上,生成器代表着正在生成某些东西的事实,这仅适用于真正的 for 理解(yield荷兰国际集团的东西)。

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

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

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.

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:

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:

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

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

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

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

输什么也不输骨气 2024-10-09 20:38:49

该部分

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 _)  
}  

(当然,如果您的 do.. 方法返回某些内容,您可以使用yield 代替)

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

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)

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

没︽人懂的悲伤 2024-10-09 20:38:49
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
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
北斗星光 2024-10-09 20:38:49

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

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

酷到爆炸 2024-10-09 20:38:49

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

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

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

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

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

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.

寄意 2024-10-09 20:38:49

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

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

另请注意,for 操作的计算成本相对较高(目前),因此也许更简单的迭代方法可能更合适,也许类似于:

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

更新:

如果您必须使用 for 理解并且可以接受一些限制,那么这可能会起作用:

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

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.

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.

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:

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:

for (i <- expr1; j <- i) {
  if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文