331

Ruby と Python の歩留まりがよくわかりました。Scala の yield は何をしますか?

4

10 に答える 10

855

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

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

Scala のfor内包表記は、マップflatMapfilter. またはforeach。Scala は実際にfor-expression をこれらのメソッドの呼び出しに変換するため、それらを提供する任意のクラスまたはそれらのサブセットを内包表記に使用できます。

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

  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、条件はすぐには適用されません。代わりに、各要素が によって要求されるforeachと、filterは条件をテストします。これにより、foreachを介して要素に影響を与えることができますfound。明確にするために、同等の for-comprehension コードを次に示します。

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 の間で non-strict から strict に変更されました。

于 2009-06-29T17:33:01.893 に答える
211

これは、シーケンス内包表記で使用されます(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 

リンクで。

Ruby'syieldには別の効果があります。

于 2009-06-27T09:42:19.530 に答える
15

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

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

何かのようなもの:

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

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

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

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

于 2009-06-27T09:52:58.917 に答える
13

理解のために次のことを考慮してください

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

次のように声に出して読むと役立つ場合があります。

「各整数についてi、それが より大きい場合3生成(生成)iし、リストに追加しますA。」

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

セット表記

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

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

または代わりに

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

于 2015-12-23T22:23:44.043 に答える
12

yieldScalaのキーワードmapは、Daniel Sobral がすでに詳細に説明したように、簡単に a に置き換えることができる単純な構文糖衣です。

一方、Pythonのジェネレーター (または継続) に似たジェネレーター (または継続) を探しているyield場合は、絶対に誤解を招く可能性があります。詳細については、この SO スレッドを参照してください: Scala で 'yield' を実装するための推奨される方法は何ですか?

于 2012-07-07T12:27:09.617 に答える
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 つのコードも同等です。

Map は yield と同じくらい柔軟で、その逆も同様です。

于 2013-10-25T23:59:31.533 に答える
-3

yield は 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 )

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

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

于 2013-10-10T18:24:46.153 に答える