S-JIS[2010-12-01/2013-06-08] 変更履歴

Scala for

Scalaのfor式について。


forの基本形

コレクション(配列やリスト・セット・マップ等)の各要素を順番に処理する。

for (変数 <- コレクション)for (変数 <- コレクション) { 式;… }

for式において、「各要素を順番に返すもの」(「変数<-コレクション」)をジェネレーターと呼ぶらしい。
「(繰り返し用の)値を生成する」のでジェネレーターなのかな?

備考
scala> for (s <- Array("a","b","c")) println(s)
a
b
c
scala> val list = List(3,4,5)
list: List[Int] = List(3, 4, 5)

scala> for (i <- list) println(i)
3
4
5
Javaのforeachと似ている。
for (Integer i : list) {
  System.out.println(i);
}
scala> val r = for (i <- list) i
r: Unit = ()
for自体の戻り値は無い(戻り型はUnit)。
scala> for (i <- list) i = 9
<console>:6: error: reassignment to val
       for (i <- list) i = 9
                         ^
ループ用の変数は暗黙にvalで宣言されている。
(つまり再代入不可)
scala> for (val i <- list) println(i)
warning: there were deprecation warnings; re-run with -deprecation for details
3
4
5
変数に明示的にvalを付けることも可能。
だが、警告が出る(苦笑)ので推奨されていない模様。
scala> for (i:Int <- list) println(i)
3
4
5
scala> for (i:Long <- list) println(i)
<console>:7: error: type mismatch;
 found   : (Long) => Unit
 required: (Int) => ?
       for (i:Long <- list) println(i)
                   ^
変数の型を明示的に指定することも可能。
(コレクションの要素の型と違っていたら、コンパイルエラー)
scala> val tupleList = List((1,"one"),(2,"two"),(3,"three"))
tupleList: List[(Int, java.lang.String)] = List((1,one), (2,two), (3,three))

scala> for ((n,s) <- tupleList) printf("%d = %s%n", n, s)
1 = one
2 = two
3 = three
タプルのリストをforで使ってみた。
(変数にパターンマッチング機能が使える)
scala> val map = Map(1 -> "one", 2 -> "two", 3 -> "three")
map: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,one), (2,two)
, (3,three))

scala> for ((k,v) <- map) printf("%d = %s%n", k, v)
1 = one
2 = two
3 = three

scala> for (t <- map) println(t.getClass)
class scala.Tuple2
class scala.Tuple2
class scala.Tuple2
マップをジェネレーターに指定すると、キーと値をペアにしたタプルを返すようだ。
scala> val list = List(1->"o", 2->"t", 3->"t", 4->"f", 5->"f")
list: List[(Int, java.lang.String)] = List((1,o), (2,t), (3,t), (4,f), (5,f))

scala> for ((n, "t") <- list) println(n)
2
3

scala> for (t @ (_, "f") <- list) println(t)
(4,f)
(5,f)
パターンマッチでは、マッチしたデータだけが対象になる。[2011-03-26]

forによる指定回数の繰り返し

回数を指定した繰り返しを行う方法。(→他言語のfor文

Scala Java(C言語) MSX-BASIC 備考
1から10まで繰り返す
for (i <- 1 to 10) {
  println(i)
}
for (int i = 1; i <= 10; i++) {
  System.out.println(i);
}
FOR I = 1 TO 10
  PRINT I
NEXT I
ちなみに、配列の添字はJava・Scalaは0から始まりBASICは1から始まる。
したがって、Javaは0からループしBASICは1からループする方が都合がいい。
Scalaで配列を扱うなら、添字の番号を使うのではなく、イテレートにより繰り返すのが望ましいらしい。
0から9まで繰り返す
for (i <- 0 until 10) {
  println(i)
}
for (int i = 0; i < 10; i++) {
  System.out.println(i);
}
FOR I = 0 TO 9
  PRINT I
NEXT I
10回繰り返す 上側の方法? 下側の方法 上側の方法
逆順
for (i <- 9 to 0 by -1) {
  println(i)
}
for (int i = 9; i >= 0; i--) {
  System.out.println(i);
}
FOR I = 9 TO 0 STEP -1
  PRINT I
NEXT I
 
 
scala> for(_ <- 1 to 3) println("zzz")
zzz
zzz
zzz
    ループ(イテレート)する変数を使用しない場合は「_」を使うことが出来る。[2010-12-25]
 
for (val i <- 1 to 10) println(i)
    ループの変数にvalを付けることが出来たが、
Scala2.10で付けられなくなった。[2013-06-08]

この、Scalaでのforを使った繰り返し回数の書き方が、非常に興味深い。

元々for文は、BASICでの使い方のように、10回処理を繰り返す為に「変数を1から10まで1ずつ変化させる」ものだったのだと思う。
さらに「2ずつ増やしたい」とか「逆順で変化させたい」といった要望に応えるために「STEP」が追加されたんじゃないかな。

rem BASIC
FOR I = 1 TO 10 STEP 1
  PRINT I
NEXT

これをC言語はもっと汎用化させ、初期化(i = 0)・繰返条件(i < 10)・増分指定(i++)をそれぞれもっと自由に記述できるようにした。
例えば「2倍ずつにしたい」と思ったら、BASICでは別の変数を用意する(か、トリッキーな方法を使う)ことになるが、C言語(Java)なら増分指定で「i *= 2」と書けばいい。

Scalaでの回数指定の方法は、一見、BASICと非常によく似ている。
「i <- 1」と「I = 1」、「to 10」と「TO 10」、「by 1」と「STEP 1」がそれぞれ対応しているように“見える”。
しかしScalaでは、「i <- 1」と「to 10」で分かれているのではなく、実際は「i <-」と「1 to 10」で分かれている。
実は「1 to 10」は独立した式(演算)なので、「1 to 10」を実行することが出来る。REPLで表示すると以下のようになる。

scala> 1 to 10
res32: scala.collection.immutable.Range.Inclusive with scala.collection.immutable.Range.ByOne = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> 0 until 10
res34: scala.collection.immutable.Range with scala.collection.immutable.Range.ByOne = Range(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> 9 to 0 by -1
res33: scala.collection.immutable.Range = Range(9, 8, 7, 6, 5, 4, 3, 2, 1, 0)

つまり「1 to 10」は、Rangeインスタンスを返すメソッド呼び出しになっているのだ。
だから「1 to 10」を使ったfor文は、実際にはRangeインスタンスをジェネレーターに指定していることになる。

scala> for (i <- Range(1, 10)) println(i)

だから、一見BASIC風の記述に見えても、実は基本形と全く変わっていないということになる。
(Scalaのforの文法としてtoやuntilやbyが定義されているわけではない)

ちなみに、この「BASIC風の記述に見える」というのもポイントのひとつ。いわゆるDSLに通じるものがあると思う。
DSLとは、限定された事柄のみを記述するための言語(特定の目的に特化した言語)。
新しい言語を作ると、それを解釈するためのインタープリターやらコンパイラーやらが必要となるが、Scala上でその文法を実現することが出来れば、新しいコンパイラー等を作る必要は無い。
Scalaのforによる繰り返し回数指定の方法は、“「to N」で終了値を指定・「by N」で増分を指定するというDSL”を実現しているとも言えるのではないか。
(しかし実のところ、自分はDSLについて全く分かっていない(想像でしかない)ので、もしかすると全然違うかも(爆))


なお、「1 to 10」がRangeインスタンスを返すのは、ちょっとややこしい仕組みに拠る。

  1. Scalaでは、メソッドの引数が1個だけの場合、メソッド呼び出しのピリオド「.」と括弧は省略できる。
    したがって、「1 to 10」は「1 to(10)」「1.to(10)」と書くことが出来る。
    つまり「1 to 10」は、1(Int)のtoメソッドを呼んでいることになる。
  2. が、IntオブジェクトのScaladocを見てもtoメソッドは載っていない。
    で、RichIntクラスにtoメソッドがある。ちなみに以下のような感じ。
    final class RichInt(val start: Int)〜 {
        def to(end: Int): Range.Inclusive with Range.ByOne = Range.inclusive(start, end)
    }
    変数startがRichIntのコンストラクターの引数だから、「1.to(10)」の「1」。endはto()の引数だから10。で、Rangeオブジェクトのinclusive()メソッドを呼び出して(Range.inclusive(1, 10))、Rangeインスタンスを作っているわけだ。
  3. そもそも「1」がIntなのになぜRichIntクラスのメソッドが呼ばれるかと言うと、PredefオブジェクトIntからRichIntへの暗黙変換のメソッドが定義されているから。
    implicit def intWrapper(x: Int) = new runtime.RichInt(x)
  4. で、Predefの各メソッドはScalaでは自動的にインポートされている
  5. なので、Intの「1」はRichIntにラップされて、そのto()メソッドが呼ばれることになる。
    つまり「1 to 10」は「new scala.runtime.RichInt(1).to(10)」と等しい。
  6. ついでに「9 to 0 by -1」のbyは、Rangeクラスのby()メソッドが別途呼ばれている。
    つまり「(9 to 0).by(-1)」「{ val r = new scala.runtime.RichInt(9).to(0); r.by(-1) }」「new scala.runtime.RichInt(9).to(0).by(-1)」と等しい。

しかし「start.to(end)」は最終的には「Range.inclusive(start, end)」内でRangeインスタンスが作られるけど、間に使い捨てのRichIntインスタンス生成が入ったりするのが、ちょっと無駄な気もする…。
コンパイル時に上手く最適化してくれればいいんだけど、さすがに無理だよな(苦笑) →最近のVMは実行時に上手く最適化してくれるらしい![2011-02-15]

Scala   コンパイルにより生成されたJava
 
import scala.Predef$;
import scala.ScalaObject;
import scala.collection.immutable.Range;
import scala.runtime.RichInt;
def main(args: Array[String]) {
  val r1 = 0 to 9
  println(r1)
  val r2 = 0 until 10
  println(r2)
  val r3 = 9 to 0 by -1
  println(r3)
}
public void main(String args[]) {
  scala.collection.immutable.Range.Inclusive r1 = Predef$.MODULE$.intWrapper(0).to(9);
  Predef$.MODULE$.println(r1);
  scala.collection.immutable.Range.ByOne r2 = Predef$.MODULE$.intWrapper(0).until(10);
  Predef$.MODULE$.println(r2);
  Range r3 = Predef$.MODULE$.intWrapper(9).to(0).by(-1);
  Predef$.MODULE$.println(r3);
}

intWrapper()の呼び出しも、「引数が同じ場合に最適化されて1回だけの呼び出しに共通化」されたりとかはしないか〜。
副作用が無く、 不変オブジェクトを返すメソッドなら その戻り値は使い回していいはずだけど、「intWrapper()」がそういうメソッドなのかどうかを判断することは出来ないもんな。(C++なら副作用の無い関数に付けるconst指定があるけど、Scalaにもその手の指定方法は無さそう)
まぁ、最近のオブジェクト指向言語を使うなら、インスタンス生成を嫌がっちゃいかんわな。もうJDK1.4時代じゃないし、実行時最適化もあるし。


forのフィルター(if)

for式では、ジェネレーターの直後に条件(フィルター)を書くことが出来る。[2010-12-02]
指定された条件を満たす場合だけ、forの本体の式が実行される。

for (ジェネレータ if 条件)for (ジェネレータ if 条件) { 式;… }
備考
scala> for (i <- 0 until 10 if i % 2 == 0) println(i)
0
2
4
6
8
 
scala> for (i <- 0 until 10; if i % 2 == 0) println(i)
ジェネレーターと「if」の間にセミコロンを入れてもよい。
scala> for (i <- 0 until 10 if i%2==0; if i%3==0) println(i)
0
6
ifを複数書くことが出来る。セミコロンで区切る。
それら全てが満たされる場合だけ本体が実行される。(AND条件)
 
scala> for (i <- 0 until 10
     | if i % 2 == 0
     | if i % 3 == 0)
     | println(i)
0
6
ifを複数行に分けて書く場合は、セミコロンは不要。
scala> for (i <- 1 to 10; val n = i * i if n%3==0) println(i,n)
(3,9)
(6,36)
(9,81)
for(i <- 1 to 20; val n=i%3; val m=i%4 if n==m) println(i,n,m)
ジェネレーターと「if」の間にローカル変数の宣言を入れることも出来る。[2011-03-26]
「if」の直前のセミコロンは省略可能。
複数の変数宣言をセミコロン区切りで記述可能。

値を返すfor(yield)

基本的なfor式では戻り値は無い(戻り型がUnitなので常に「()」が返る)が、yieldを付けることによって、繰り返した本体の演算結果を返すことが出来る。[2010-12-02]

for () yieldfor () yield  { 式;… }

繰り返された本体の計算結果がコレクション(List等)に順番に入れられて返ってくる。

備考
scala> for (i <- 1 to 5) yield i
res4: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 4, 5)
scala> for (i <- 1 to 5) yield "i=" + i
res5: scala.collection.immutable.IndexedSeq[java.lang.String] = Vector(i=1, i=2,
i=3, i=4, i=5)
実際に何のクラスが返るのかは、
ジェネレーターによって異なる。
(Scala2.8.0)
scala> for (s <- Array("a","b","c")) yield s
res6: Array[java.lang.String] = Array(a, b, c)
scala> for (s <- List("a","b","c")) yield s
res7: List[java.lang.String] = List(a, b, c)
scala> val map = Map(1->"one", 2->"two", 3->"three")
map: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,one), (2,two)
, (3,three))

scala> for((k,v)<-map) yield k
res8: scala.collection.immutable.Iterable[Int] = List(1, 2, 3)

scala> for((k,v)<-map) yield v
res9: scala.collection.immutable.Iterable[java.lang.String] = List(one, two, thr
ee)

二重ループ

ジェネレーターを複数書くことで多重ループを1つのforで記述することが出来る。[2010-12-02]

for (ジェネレータ1; ジェネレータ2; …)for (ジェネレータ1; ジェネレータ2; …) { 式;… }
備考
scala> for (i <- 1 to 2; j <- 1 to 3) printf("%d-%d%n", i, j)
1-1
1-2
1-3
2-1
2-2
2-3
scala> for (i <- 1 to 2; j <- 1 to 3) yield "%d-%d".format(i, j)
res29: scala.collection.immutable.IndexedSeq[String] = Vector(1-1, 1-2, 1-3, 2-1
, 2-2, 2-3)
セミコロンで区切って複数のジェネレーターを記述する。
scala> for (i <- 1 to 2
     |      j <- 1 to 3) printf("%d-%d%n", i, j)
<console>:2: error: ')' expected but '<-' found.
            j <- 1 to 3) printf("%d-%d%n", i, j)
              ^
scala> for (i <- 1 to 2;
     |      j <- 1 to 3) printf("%d-%d%n", i, j)
scala> for (i <- 1 to 2
     |     ;j <- 1 to 3) printf("%d-%d%n", i, j)
ループの各変数を複数行に分けて書く場合も、区切りのセミコロンは省略できない。
scala> for {i <- 1 to 2
     |      j <- 1 to 3} printf("%d-%d%n", i, j)
forの直後のブロックを丸括弧でなく波括弧にすると、
改行時に区切りのセミコロンを省略できる。[2011-03-26]
scala> for (i <- 1 to 2 if i%2==0; j <- 1 to 3) printf("%d-%d%n", i, j)
2-1
2-2
2-3
scala> for (i <- 1 to 2; j <- 1 to 3 if i%2==0) printf("%d-%d%n", i, j)
2-1
2-2
2-3
フィルターはどのジェネレーターの直後にも付けることが出来るようだ。
(が、先頭のフィルターにする方が効率はいいだろうな)
scala> for (i <- 1 to 2 if j%2==0; j <- 1 to 3) printf("%d-%d%n", i, j)
<console>:6: error: not found: value j
       for (i <- 1 to 2 if j%2==0; j <- 1 to 3) printf("%d-%d%n", i, j)
                           ^
さすがに、後で定義する変数を先のフィルターで使うことは出来ない。
(当たり前か^^;)
scala> for (i <- 0 to 10 if i%2==0; if i%3==0; j <- 1 to 3)
     | printf("%d-%d%n", i, j)
0-1
0-2
0-3
6-1
6-2
6-3
scala> for (i <- 1 to 2 if i % 2 == 0;
     |      j <- 1 to 3 if j % 3 == 0)
     |   printf("%d-%d%n", i, j)
2-3
複数のフィルターを使う例。

for式の実体


Scala構文へ戻る / Scala目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま