Ruby と Python の歩留まりがよくわかりました。Scala の yield は何をしますか?
10 に答える
受け入れられた答えは素晴らしいと思いますが、多くの人がいくつかの基本的な点を理解できていないようです.
まず、Scala のfor
内包表記は Haskell の表記法と同等do
であり、複数のモナド操作を構成するための構文糖衣にすぎません。このステートメントは、助けが必要な人の役に立たない可能性が高いので、もう一度試してみましょう… :-)
Scala のfor
内包表記は、マップflatMap
とfilter
. またはforeach
。Scala は実際にfor
-expression をこれらのメソッドの呼び出しに変換するため、それらを提供する任意のクラスまたはそれらのサブセットを内包表記に使用できます。
まず、翻訳について話しましょう。非常に単純なルールがあります。
これ
for(x <- c1; y <- c2; z <-c3) {...}
に翻訳されます
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))
これ
for(x <- c1; y <- c2; z <- c3) yield {...}
に翻訳されます
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))
これ
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
。詳細については、以下のセクションを参照してください。これ
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
動作を変更することなく、ほとんどの人が期待する結果が得られます。補足として、Range
Scala 2.7 と Scala 2.8 の間で non-strict から strict に変更されました。
これは、シーケンス内包表記で使用されます(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
には別の効果があります。
Scala ユーザー (私はそうではありません) からより良い回答が得られない限り、私の理解は次のとおりです。
for
これは、既存のリストから新しいリストを生成する方法を示す で始まる式の一部としてのみ表示されます。
何かのようなもの:
var doubled = for (n <- original) yield n * 2
したがって、入力ごとに 1 つの出力項目があります (ただし、重複を削除する方法はあると思います)。
これは、他の言語のyieldによって有効になる「命令型継続」とはまったく異なります。これは、ほとんどすべての構造を持つ命令型コードから、任意の長さのリストを生成する方法を提供します。
(C# に精通している場合は、よりもLINQ の select
演算子に近いですyield return
)。
理解のために次のことを考慮してください
val A = for (i <- Int.MinValue to Int.MaxValue; if i > 3) yield i
次のように声に出して読むと役立つ場合があります。
「各整数についてi
、それが より大きい場合は3
生成(生成)i
し、リストに追加しますA
。」
数学的なセット ビルダー表記法に関しては、上記の for-comprehension は
これは次のように読むことができます
「各整数について、それがより大きい場合、それは集合のメンバーです。」
または代わりに
"は、それぞれが より大きいようなすべての整数の集合です。"
yield
Scalaのキーワードmap
は、Daniel Sobral がすでに詳細に説明したように、簡単に a に置き換えることができる単純な構文糖衣です。
一方、Pythonのジェネレーター (または継続) に似たジェネレーター (または継続) を探しているyield
場合は、絶対に誤解を招く可能性があります。詳細については、この SO スレッドを参照してください: Scala で 'yield' を実装するための推奨される方法は何ですか?
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 と同じくらい柔軟で、その逆も同様です。
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), これはおそらく意図したものではありません.