我问的是一个最近困扰我的基本问题。 我想编写一个Scala For表达式来执行以下操作:
for (i <- expr1) {
if (i.method) {
for (j <- i) {
if (j.method) {
doSomething()
} else {
doSomethingElseA()
}
}
} else {
doSomethingElseB()
}
}
问题在于,在多个生成器For表达式中,我不知道我可以在哪里放置表达体。
for {i <- expr1
if(i.method) // where can I write the else logic ?
j <- i
if (j.method)
} doSomething()
如何在Scala Style中重写代码?
答案 0 :(得分:18)
您编写的第一个代码完全有效,因此无需重写它。在其他地方,你说你想知道如何做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
实施的(请参阅here或其他类似的问题和答案)。这意味着上面的代码与此代码完全相同:
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-comprehensions(那些yield
某事物)
答案 1 :(得分:4)
部分
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)
这里并没有那么糟糕,但如果你有一个更深层次的嵌套结构,它可以......
答案 2 :(得分:3)
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
答案 3 :(得分:2)
你做不到。 for(expr; if)构造只过滤必须在循环中处理的元素。
答案 4 :(得分:1)
如果订单对doSomething()和doSomethingElse()的调用不重要,那么你可以像这样重新安排代码。
val (tmp, no1) = expr1.partition(_.method)
val (yes, no2) = tmp.partition(_.method)
yes.foreach(doSomething())
no1.foreach(doSomethingElse())
no2.foreach(doSomethingElse())
为了回答你原来的问题,我认为对于具体的用例而言,理解可能会非常好,而你的例子并不适合。
答案 5 :(得分:0)
Scala for operation中指定的条件用于过滤生成器中的元素。不满足条件的元素将被丢弃,不会显示在yield / code块中。
这意味着如果您想基于条件表达式执行备用操作,则需要将测试推迟到yield / code块。
另外请注意,for操作的计算成本相对较高(当前),因此可能更简单的迭代方法可能更合适,可能是这样的:
expr1 foreach {i =>
if (i.method) {
i foreach {j =>
if (j.method)
doSomething()
else
doSomethingElseA()
}
}
else
doSomethingElseB()
}
更新
如果你必须使用for comprehension并且你可以忍受一些限制,这可能会有效:
for (i <- expr1; j <- i) {
if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}