S-JIS[2011-02-15] 変更履歴
Scalaのfor式は、foreach・filter・mapメソッド等に変換されて実行される。
|
Java(やC言語)のfor文は、while文と対応付けることが出来る。
Javaのfor文 | 対応するwhile文 | 備考 |
---|---|---|
for (初期化部; 繰返条件; 増分) { |
{ |
ただし、continueの行き先だけは異なる。 |
for (int i = 0; i < 10; i++) { System.out.println(i); } |
{ int i = 0; while (i < 10) { System.out.println(i); i++; } } |
しかしScalaのfor式はwhile式とは全く異なり、コレクションのforeachやfilter・mapメソッド等に置き換えられて実行される。
厳密にはコレクションとも無関係で、foreach・filter・map等のメソッドが用意されているクラスであれば何でもいいらしい。
for式が実際にどう変換されるか試してみた。
(REPL起動時にscalaコマンドに-Xprint:namerを指定して確認)
for式 | 変換結果(イメージ) | 備考 |
---|---|---|
for (i <- 0 until 10) { println(i) } |
(0 until 10).foreach { (i) => println(i) } |
値を返さないfor式はforeachに変換される。 |
var sum = 0 for (i <- List(1,2,3)) { sum += i } |
var sum = 0 List(1,2,3).foreach { (i) => sum += i } |
|
for (i <- 0 until 10 if i % 2 == 1) { println(i) } |
(0 until 10).withFilter { (i) => i % 2 == 1 }.foreach { (i) => println(i) } |
withFilterはfilterと同等の処理を行うがfilterより高速。 ただし後にmapやforeachなど限られたメソッドが続く場合しか使えない。 というか、正にそういうフィルターの為にあるんだろう。 scala> benchmark(10){ Range(1,100*10000).filter(_ % 2 ==1).foreach(i=>i) } res0: List[Long] = List(188, 172, 187, 172, 188, 312, 297, 282, 172, 172) scala> benchmark(10){ Range(1,100*10000).withFilter(_ % 2 ==1).foreach(i=>i) } res1: List[Long] = List(125, 125, 109, 110, 109, 109, 110, 109, 125, 125) |
for (i <- 1 to 3; j <- 2 to 4) { println(i * j) } |
(1 to 3).foreach { (i) => (2 to 4).foreach { (j) => println(i * j) } } |
二重ループ。 |
for (i <- 1 to 10) yield { i.toString } |
(1 to 10).map { (i) => i.toString } |
値を返すfor式(yield)ではmapに変換される。 |
for (i <- 1 to 3; j <- 2 to 4; k <- 3 to 5) yield { i * j * k } |
(1 to 3).flatMap { (i) => (2 to 4).flatMap { (j) => (3 to 5).map { (k) => i * j * k } } } |
多重ループ(ジェネレーターが複数ある)のyieldの場合、 一番内側のみがmapで、それ以外はflatMapになる。 |
自作クラスでfor式が使えるかどうか試してみた。
for式から変換される為のメソッド群が定義されている抽象クラスやトレイトは存在しない。
(TraversableやIterableといったコレクションの親トレイトを継承する必要も無い)
どうやら、メソッド名(と型の整合性)だけ合致していれば良いようだ。
自作クラス | for式 | 備考 |
---|---|---|
class Sample0 |
for (i <- new Sample0) { println(i) } error: value foreach is not a member of Sample0 |
とりあえず何も定義していないクラスをfor式で使ったら foreachが無いというエラー。 |
class Sample1 { type A = Int def foreach[U](f: A => U): Unit = { f(11); f(22); f(33) } } |
for (i <- new Sample1) { println(i) } 11 |
|
for (i <- new Sample1 if i % 2 == 1) { println(i) } error: value withFilter is not a member of Sample1 |
フィルター(if)を書いてみたら、withFilterが無いというエラー。 | |
class Sample2 { type A = Int private val arr = Array(11,22,33) def foreach[U](f: A => U): Unit = { f(arr(0)); f(arr(1)); f(arr(2)) } def filter(p: A => Boolean): Sample2 = { val s = new Sample2 if (!p(arr(0))) s.arr(0) = 0 if (!p(arr(1))) s.arr(1) = 0 if (!p(arr(2))) s.arr(2) = 0 s } } |
for (i <- new Sample2 if i % 2 == 1) { println(i) } 11 |
withFilterは定義していないのだが、 filterを定義すればフィルター(if)も使えるようだ。 scalacのコンパイルオプション-Xprint:namerを使って確認すると、withFilterの呼び出しになっている。 しかし「warning: `withFilter' method does not yet exist on Sample2, using `filter' method instead(withFilterが無いから代わりにfilter使うよ)」という警告が出て、 実際に生成されるクラスではfilterの呼び出しになる。 for式を使わず直接withFilterを呼び出した場合でも、同様になる。 (Eclipseでは、withFilterの上でF3を押すとfilterへジャンプする!) |
class Sample3 { type A = Int def filter(p: A => Boolean) = { val a = Array("aa","bb","cc") if (!p(11)) a(0) = "z" if (!p(22)) a(1) = "z" if (!p(33)) a(2) = "z" a } } |
for (i <- new Sample3 if i % 2 == 1) { println(i) } aa |
filterだけ定義して別の(foreachを持っている)クラスを返すのも問題ないようだ。 |
class Sample0 |
for (i <- new Sample0) yield { i * 2 } error: value map is not a member of Sample0 |
今度はyieldを試してみる。 まずは予想通りのエラー(笑) |
class Sample4 { type A = Int def map(f: A => A) = { Array[A](f(11), f(22), f(33)) } } |
for (i <- new Sample4) yield { i * 2 } Array(22, 44, 66) |
|
for (i <- new Sample4; j <- new Sample4) yield { i + j } error: value flatMap is not a member of Sample4 |
||
class Sample5 { type A = Int def flatMap(f: A => Array[A]) = { import scala.collection.mutable._ val a = ArrayBuffer[A]() a ++= f(11) a ++= f(22) a ++= f(33) a } def map(f: A => A) = { Array[A](f(1), f(2), f(3)) } } |
for (i <- new Sample5; j <- new Sample5) yield { i + j } ArrayBuffer(12, 13, 14, 23, 24, 25, 34, 35, 36) |
二重ループの場合、flatMapの中の関数fがmapの呼び出しになる。 したがって、mapの戻り型と、flatMapの引数fが期待する戻り型が一致している必要がある。 三重ループだと、flatMapがさらにflatMapに渡されるから…ややこしい(苦笑) |