S-JIS[2011-02-15] 変更履歴

Scala for式の実体

Scalafor式は、foreach・filter・mapメソッド等に変換されて実行される。


前置き

Java(やC言語)のfor文は、while文と対応付けることが出来る。

Javaのfor文 対応するwhile文 備考
for (初期化部; 繰返条件; 増分) {
  本文
}
{
  初期化部
  while (繰返条件) {
    本文
    増分
  }
}
ただし、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式の変換結果

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)
}
withFilterfilterと同等の処理を行うが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式が使えるかどうか試してみた。

for式から変換される為のメソッド群が定義されている抽象クラスやトレイトは存在しない。
TraversableIterableといったコレクションの親トレイトを継承する必要も無い)

どうやら、メソッド名(と型の整合性)だけ合致していれば良いようだ。

自作クラス 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
22
33
 
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
0
33
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
z
cc
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に渡されるから…ややこしい(苦笑)

for式へ戻る / コレクションへ戻る / Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま