web-dev-qa-db-ja.com

理解のために「if..else」を行うにはどうすればよいですか?

私は最近私を混乱させた非常に基本的な質問をしています。 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スタイルでコードを書き直すにはどうすればよいですか?

19
Sawyer

最初に書いたコードは完全に有効なので、書き直す必要はありません。他の場所では、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の部分を修正するだけで済みます。 validElementsIndexedSeqを返しているので、それを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のコンテキストでのみ意味があります。そして、実際には、generatorsは、何かが生成されているという事実を表しています。これは、真の理解にのみ当てはまります( yielding何か)。

19

一部

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を使用できます)

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

4
Landei
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
oxbow_lakes

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

2
Nicolas

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

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

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

あなたの最初の質問に答えるために、私は、理解のために特定のユースケースにとって非常に良いかもしれないと思います、そしてあなたの例はうまく適合しません。

1
Craig P. Motlin

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

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

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

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()
}
0
Don Mackenzie