理解のために「if..else」をどうすればいいですか?
-
10-10-2019 - |
質問
私は最近私を混乱させた非常に基本的な質問をしています。次のようなことをするために表現のためにScalaを書きたいです:
for (i <- expr1) {
if (i.method) {
for (j <- i) {
if (j.method) {
doSomething()
} else {
doSomethingElseA()
}
}
} else {
doSomethingElseB()
}
}
問題は、表現のための複数のジェネレーターで、それぞれを表現本体にどこに置くことができるかわからないことです。
for {i <- expr1
if(i.method) // where can I write the else logic ?
j <- i
if (j.method)
} doSomething()
Scalaスタイルでコードを書き換えるにはどうすればよいですか?
解決
あなたが書いた最初のコードは完全に有効なので、それを書き直す必要はありません。他の場所では、あなたがそれをやる方法を知りたいと言ったと言いました。実際には「Scala-Style」はありませんが、より機能的なスタイルを想定し、それをタックします。
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
これは、提示したループと条件とまったく同じ組み合わせを行いますが、より読みやすく、変更しやすいです。
利回りについて
提示された問題について1つに注意することが重要だと思います。それを簡素化しましょう:
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
最初のスニペットは2つで実装されます map
コール、2番目のスニペットは1つを使用します flatMap
そして1つ 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; if)constructは、ループで処理する必要がある要素をフィルタリングするだけです。
dosomething()およびdosomeTheselse()への呼び出しに対して注文が重要でない場合、このようなコードを再配置できます。
val (tmp, no1) = expr1.partition(_.method)
val (yes, no2) = tmp.partition(_.method)
yes.foreach(doSomething())
no1.foreach(doSomethingElse())
no2.foreach(doSomethingElse())
あなたの元の質問に答えるために、私は、包括的に特定のユースケースに非常に優れている可能性があり、あなたの例がうまく適合しないと思います。
発電機の要素をフィルタリングするために、操作法のためのScalaで指定された条件。条件を満たさない要素は破棄され、収量 /コードブロックには提示されません。
これが意味することは、条件付き式に基づいて代替操作を実行する場合、テストは収量 /コードブロックに延期する必要があるということです。
また、操作のための操作は(現在)計算するのに比較的高価であることに注意してください。おそらく、より単純な反復アプローチがより適切かもしれません。
expr1 foreach {i =>
if (i.method) {
i foreach {j =>
if (j.method)
doSomething()
else
doSomethingElseA()
}
}
else
doSomethingElseB()
}
アップデート:
理解のために使用する必要があり、いくつかの制限で暮らすことができれば、これはうまくいくかもしれません。
for (i <- expr1; j <- i) {
if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}