最初に書いたコードは完全に有効なので、書き直す必要はありません。他の場所で、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
しかし、classifyElements 自体が を返し、コレクションを返す必要があるため、それだけでは機能しません。ここで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
map
最初のスニペットは 2 つの呼び出しで実装されますが、2 番目のスニペットは oneflatMap
と oneを使用しますmap
。
そのため、ループのネストや複数のジェネレーターの使用yield
について心配することさえ意味があるという文脈でのみです。for
実際、ジェネレーターは、何かが生成されているという事実を表します。これは、真の for 内包表記 (何かをyield
ing するもの) にのみ当てはまります。