24

最近私を混乱させた非常に基本的な質問をしています。次のようなことを行う Scala For 式を書きたいと思います。

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 スタイルでコードを書き直すにはどうすればよいですか?

4

6 に答える 6

22

最初に書いたコードは完全に有効なので、書き直す必要はありません。他の場所で、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、コレクションを返すので問題ありません。その部分を修正するだけですelsevalidElementsは を返すので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 内包表記 (何かをyielding するもの) にのみ当てはまります。

于 2010-11-16T17:31:36.917 に答える
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を使用できます)

ここではそれほど便利ではありませんが、より深いネストされた構造がある場合は、...

于 2010-11-16T10:53:57.087 に答える
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
于 2010-11-16T20:42:14.983 に答える
2

それはいけません。for(expr; if) コンストラクトは、ループで処理する必要がある要素をフィルタリングするだけです。

于 2010-11-16T08:28:24.523 に答える
1

doSomething()およびdoSomethingElse()の呼び出しで順序が重要でない場合は、次のようにコードを並べ替えることができます。

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

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

あなたの最初の質問に答えるために、私は、理解のために特定のユースケースに非常に適している可能性があり、あなたの例はうまく適合しないと思います。

于 2010-11-16T13:47:16.817 に答える
0

操作の Scala で指定された条件は、ジェネレーターから要素をフィルター処理するように機能します。条件を満たさない要素は破棄され、yield / コード ブロックに提示されません。

これが意味することは、条件式に基づいて代替操作を実行したい場合、テストを yield / コード ブロックまで延期する必要があるということです。

また、 for 操作は (現在) 計算に比較的コストがかかることに注意してください。そのため、おそらく次のような単純な反復アプローチがより適切である可能性があります。

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()
}
于 2010-11-16T09:32:37.370 に答える