我怎样才能在 for 理解中执行“if..else”?
-
10-10-2019 - |
题
我问一个非常基本的问题,最近让我很困惑。我想编写一个 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)
类型 result
有 Array[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()
}