Scalaの利回りはどれくらいですか?


回答:


205

これは、シーケンス内包表記(Pythonのリスト内包表記やジェネレーターなどyield)で使用されます。

組み合わせて適用さforれ、結果のシーケンスに新しい要素を書き込みます。

簡単な例(scala-langから)

/** Turn command line arguments to uppercase */
object Main {
  def main(args: Array[String]) {
    val res = for (a <- args) yield a.toUpperCase
    println("Arguments: " + res.toString)
  }
}

F#の対応する式は次のようになります。

[ for a in args -> a.toUpperCase ]

または

from a in args select a.toUpperCase 

Linqで。

Rubyのyield効果は異なります。


57
では、なぜ地図の代わりに歩留りを使うのでしょうか?このマップコードは同等ですval res = args.map(_。toUpperCase)、そうですか?
ジオ

4
構文の方が好きな場合。また、alexeyが指摘するように、内包表記は、flatMap、filter、foreachにアクセスするための優れた構文も提供します。
ネイサンShively-Sanders

22
正しい。単純なマップ(ifを持たないジェネレーターが1つだけ)がある場合、mapを呼び出す方が読みやすくなります。互いに依存している複数のジェネレーターやフィルターがある場合は、式を使用することをお勧めします。
Alexey Romanov

13
与えられた例はマップ式と同等ではないことに注意してください:同じです。for内包表記は、map、flatMap、filterの呼び出しに変換されます。
ダニエルC.ソブラル

9
答えは次のように始まります:「シーケンス内包表記(Pythonのリスト内包表記やジェネレーターなど、yieldも使用できる場合)で使用されます。」これにより、Scalaの歩留まりはPythonの歩留まりに似ていると誤解されます。これはそうではありません。Pythonでは、コルーチン(または継続)のコンテキストでyieldが使用されますが、Scalaではそうではありません。詳細については、次のスレッドをご覧ください。stackoverflow.com
Richard Gomes '30

817

受け入れられた答えは素晴らしいと思いますが、多くの人がいくつかの基本的な点を理解できていないようです。

まず、Scalaのfor内包do表記はHaskellの表記法と同等であり、複数のモナド演算を合成するための構文糖にすぎません。このステートメントはおそらく助けを必要とする人には役に立たないので、もう一度試してみましょう…:-)

Scalaのfor内包マップで複数の操作の組成物のための構文糖である、flatMapfilter。またはforeach。Scalaは実際にfor-式をこれらのメソッドの呼び出しに変換するため、それらを提供するクラスまたはそのサブセットを理解のために使用できます。

まず、翻訳について話しましょう。非常に単純なルールがあります。

  1. この

    for(x <- c1; y <- c2; z <-c3) {...}

    に翻訳されます

    c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
  2. この

    for(x <- c1; y <- c2; z <- c3) yield {...}

    に翻訳されます

    c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
  3. この

    for(x <- c; if cond) yield {...}

    Scala 2.7でに翻訳されます

    c.filter(x => cond).map(x => {...})

    または、Scala 2.8では、

    c.withFilter(x => cond).map(x => {...})

    メソッドwithFilterが利用できない場合は前者にフォールバックしますが、利用できますfilter。詳細については、以下のセクションをご覧ください。

  4. この

    for(x <- c; y = ...) yield {...}

    に翻訳されます

    c.map(x => (x, ...)).map((x,y) => {...})

非常に単純なfor内包を見ると、map/のforeach選択肢は確かによく見えます。ただし、それらを作成し始めると、括弧やネストレベルで簡単に迷子になることがあります。それが起こるとき、for理解は通常はるかに明確です。

簡単な例を1つ示し、意図的に説明を省略します。どの構文が理解しやすかを判断できます。

l.flatMap(sl => sl.filter(el => el > 0).map(el => el.toString.length))

または

for {
  sl <- l
  el <- sl
  if el > 0
} yield el.toString.length

withFilter

Scala 2.8はと呼ばれるメソッドを導入しましたwithFilter。その主な違いは、フィルター処理された新しいコレクションを返す代わりに、オンデマンドでフィルター処理することです。filterこの方法は、コレクションの厳しさに基づいて定義され、その動作を持っています。これをよりよく理解するために、List(strict)とStream(non-strict)を備えたいくつかのScala 2.7を見てみましょう。

scala> var found = false
found: Boolean = false

scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9

scala> found = false
found: Boolean = false

scala> Stream.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3

ので、違いが起こりfilter、すぐに適用されてListいるので-オッズのリストを返す、foundですfalse。そのときだけforeach実行されますが、この時点でfoundは、filterすでに実行されているように、変更は無意味です。

の場合Stream、条件はすぐには適用されません。代わりに、各要素がによって要求されるとforeachfilterは条件をテストします。これによりforeach、を介して要素に影響を与えることができますfound。明確にするために、同等の理解度コードは次のとおりです。

for (x <- List.range(1, 10); if x % 2 == 1 && !found) 
  if (x == 5) found = true else println(x)

for (x <- Stream.range(1, 10); if x % 2 == 1 && !found) 
  if (x == 5) found = true else println(x)

人々はif事前にコレクション全体に適用されるのではなく、オンデマンドと見なされることを期待していたため、これは多くの問題を引き起こしました。

Scala 2.8が導入されましたwithFilter。これは、コレクションの厳密さに関係なく、常に厳密ではありません。次の例はList、Scala 2.8の両方のメソッドを示しています。

scala> var found = false
found: Boolean = false

scala> List.range(1,10).filter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3
7
9

scala> found = false
found: Boolean = false

scala> List.range(1,10).withFilter(_ % 2 == 1 && !found).foreach(x => if (x == 5) found = true else println(x))
1
3

これにより、filter動作を変更することなく、ほとんどの人が期待する結果が得られます。補足として、RangeScala 2.7とScala 2.8の間で非厳密から厳密に変更されました。


2
Scala 2.8にはwithFilterという新しいメソッドがあります。for(x <-c; if cond)yield {...}は、scala2.8ではc.withFilter(x => cond).map(x => {...})に変換されます。
Eastsun

2
@Eastsun確かにそうですが、自動フォールバックもあります。withFilter厳格なコレクションであっても、厳格ではないと想定されているため、説明が必要です。私はこれを検討します...
ダニエルC.ソブラル2010年

2
@Daniel:Oderskyらによる「Programming in Scala」では、まさにこの主題の優れた扱いがあります。(私はあなたがすでにそれを知っていると確信しています)。表示するための+1。
ラルフ

最初の2つの点は正しいです。1。for(x <- c; y <- x; z <-y) {...}は次のように翻訳されますc.foreach(x => x.foreach(y => y.foreach(z => {...}))) 。2。for(x <- c; y <- x; z <- y) yield {...}は次のように翻訳されますc.flatMap(x => x.flatMap(y => y.map(z => {...})))
Dominik

これはfor(x <- c; y = ...) yield {...}実際に翻訳されc.map(x => (x, ...)).map((x,y) => {...})ますか?翻訳されたと思いますc.map(x => (x, ...)).map(x => { ...use x._1 and x._2 here...})か、何か不足していますか?
prostynick 2016年

23

はい、Earwickerが言ったように、これselectはLINQ とほぼ同等であり、RubyやPythonとはほとんど関係がありませんyield。基本的に、C#ではどこに書き込むか

from ... select ??? 

Scalaでは代わりに

for ... yield ???

for-comprehensionsはシーケンスだけでなく、LINQのように特定のメソッドを定義する任意のタイプでも機能することを理解することも重要です。

  • あなたのタイプがちょうどを定義する場合map、それはfor単一のジェネレーターからなる式を許可します。
  • flatMapと同様に定義する場合は、複数のジェネレータで構成される式をmap許可しますfor
  • が定義されている場合はforeachfor利回りなしのループ(単一および複数のジェネレーターの両方)が許可されます。
  • それが定義されている場合filter、それが可能にfor-filter式が始まるiffor表現。

2
@Eldritch Conundrum-興味深いことに、元のSQL仕様の概要と同じ順序です。SQL言語は途中で順序を逆にしましたが、最初に何をプルしているかを記述し、その後に何を取得することが期待されるかを記述することは完全に理にかなっています。
ジョーダンパーマー2014年

13

Scalaユーザー(私はそうではありません)からより良い回答を得られない限り、これが私の理解です。

これforは、既存のリストから新しいリストを生成する方法を示すで始まる式の一部としてのみ表示されます。

何かのようなもの:

var doubled = for (n <- original) yield n * 2

したがって、各入力に対して1つの出力項目があります(ただし、重複を削除する方法はあると思います)。

これは、他の言語のyieldによって可能になる「命令型の継続」とはかなり異なります。他の言語では、ほとんどすべての構造の命令型コードから任意の長さのリストを生成する方法が提供されます。

(C#に精通している場合は、C#よりもLINQの select演算子に近くなりyield returnます)。


1
「var doubled = for(n <-original)yield n * 2」でなければなりません。
ラッセルヤン

12

キーワードyieldScalaでは、単にシンタックスシュガーで簡単に置き換えることができるmapよう、ダニエルソブラルは既に説明を詳細に。

一方、Pythonのジェネレーター(または継続)と同様のジェネレーター(または継続)を探しているyield場合は完全に誤解を招きます。詳細については、このSOスレッドを参照してください。Scalaで「yield」を実装するための推奨される方法は何ですか?


11

の理解のために考慮してください

val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i

次のように大声で読むと役立つ場合があります

についてそれぞれ整数i場合それはより大きい3、次いで収率(生産)iとリストに追加A」。

数学的なセットビルダー表記法に関して、上記のfor内包表記は、

セット表記

これは次のように読むことができます

についてそれぞれ整数私あれば、それはより大きい3、それがメンバーである集合のあ。」

または代わりに

" あは、私それぞれ私がより大きいようなすべての整数のセットです3。"


2

Yieldは、見えないバッファが含まれるforループに似ており、増分ごとに、次の項目をバッファに追加し続けます。forループの実行が完了すると、生成されたすべての値のコレクションが返されます。Yieldは、単純な算術演算子として、または配列と組み合わせて使用​​できます。以下は、理解を深めるための2つの簡単な例です。

scala>for (i <- 1 to 5) yield i * 3

res:scala.collection.immutable.IndexedSeq [Int] = Vector(3、6、9、12、15)

scala> val nums = Seq(1,2,3)
nums: Seq[Int] = List(1, 2, 3)

scala> val letters = Seq('a', 'b', 'c')
letters: Seq[Char] = List(a, b, c)

scala> val res = for {
     |     n <- nums
     |     c <- letters
     | } yield (n, c)

res:Seq [(Int、Char)] = List((1、a)、(1、b)、(1、c)、(2、a)、(2、b)、(2、c)、( 3、a)、(3、b)、(3、c))

お役に立てれば!!


この古い質問(9年以上前)に答えるとき、あなたの回答がすでに提出された他のすべての回答とどのように異なるかを指摘することは役に立ちます。
jwvh 2018

私もこの言語を学んでいる初心者なので、疑いを明確にすることが重要であり、異なる答えを出さないようにしました。提案をありがとう。
Manasa

0
val aList = List( 1,2,3,4,5 )

val res3 = for ( al <- aList if al > 3 ) yield al + 1
val res4 = aList.filter(_ > 3).map(_ + 1)

println( res3 )
println( res4 )

これらの2つのコードは同等です。

val res3 = for (al <- aList) yield al + 1 > 3
val res4 = aList.map( _+ 1 > 3 )

println( res3 ) 
println( res4 )

これらの2つのコードも同等です。

地図は歩留まりと同じくらい柔軟性があり、逆も同様です。


-3

収量はmap()よりも柔軟です。以下の例を参照してください

val aList = List( 1,2,3,4,5 )

val res3 = for ( al <- aList if al > 3 ) yield al + 1 
val res4 = aList.map( _+ 1 > 3 ) 

println( res3 )
println( res4 )

収量は次のような結果を出力します:List(5、6)、これは良いことです

map()はList(false、false、true、true、true)のような結果を返しますが、これはおそらく意図したものではありません。


4
その比較は間違っています。2つの異なるものを比較しています。yieldの式は、mapの式と同じことをするものではありません。また、マップと比較した場合の歩留まりの「柔軟性」はまったく示されていません。
dotnetN00b 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.