Ruby と Python の yield は理解できました。Scala の yield は何をしますか?
ベストアンサー1
受け入れられた答えは素晴らしいと思いますが、多くの人がいくつかの基本的な点を理解していないようです。
まず、Scala のfor
内包表記は Haskell の記法と同等でありdo
、複数のモナド演算を合成するための構文糖に過ぎません。この説明はおそらく助けを必要としている人の役には立たないので、もう一度試してみましょう... :-)
Scala のfor
内包表記は、map や を使用した複数の操作の合成に対する構文糖衣です。またはflatMap
。Scalaは実際に-式をそれらのメソッドの呼び出しに変換するため、それらを提供するクラス、またはそのサブセットは for 内包表記で使用できます。filter
foreach
for
まず、翻訳についてお話ししましょう。非常に簡単なルールがあります。
これ
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
。このメソッドの主な違いは、新しいフィルタリングされたコレクションを返すのではなく、オンデマンドでフィルタリングすることです。このメソッドの動作は、コレクションの厳密さに基づいて定義されます。これをよりよく理解するために、 (strict) と(non-strict)filter
を使用した Scala 2.7 を見てみましょう。List
Stream
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 内包コードを以下に示します。
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
非厳密となる が導入されました。次の例は、Scala 2.8 で両方の方法を示しています。List
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 の間では、非厳密から厳密に変更されました。