tkawachi Blog

Scala の For

C や Java と同じように Scala にも for がある。

昔の Java におけるfor 文は、C と同じように for (初期化; 継続条件; カウンタ更新) という形しかなかった。 Java 5.0 で導入された拡張for文(for (型 変数: コレクション)の形)では java.lang.Iterable を実装したオブジェクトならなんでも繰り返しができるようになった。 便利になったなあと思ったのを覚えてる(2004年の話)。

文法

Scala で C の for 的なものをやろうとすると

// C では
for (int i = 0; i < 10; i++) { … }
// Scala では
for (i <- 0 until 10) { … }

となるので「 for (i <- 初期値 until 上限) という固定形なんだー」と最初は思ったのだが、そうではなく for (i <- 初期値.until(上限)) であり for (i <- obj) が for 式のパターンである。 ここで初期値.until(上限)Range 型の値を返す。

Scala の言語仕様によると for 式は以下の文法をもつ。

Expr1 ::= ‘for’ (‘(’ Enumerators ‘)’ | ‘{’ Enumerators ‘}’) {nl} [‘yield’] Expr
Enumerators ::= Generator {semi Enumerator}
Enumerator ::= Generator
                | Guard
                | ‘val’ Pattern1 ‘=’ Expr
Generator ::= Pattern1 ‘<-’ Expr [Guard]
Guard ::= ‘if’ PostfixExpr

Enumerator の最後のパターンで ‘val’ Pattern1 ‘=’ Expr とあるが、この val は deprecated になったようだ。 (Scala の言語仕様は更新が追いついていないらしく、現時点の最新(2.10.3)の言語仕様書は現時点で存在しない。)

for式関連メソッド

Scala の for 式は foreach(), map(), flapMap(), withFilter() が実装されていればなんでも回せる。 全てが必要なわけではなく、用いられるパターンによって必要なメソッドが決まる。

foreach() が必要なパターン。 yield なしの時。

for (i <- obj) { … }
// 書き換えると
obj.foreach { case i => … }

map() が必要なパターン。 Generator がひとつだけで yield で値を返す時。

for (i <- obj) yield { … }
// 書き換えると
obj.map { case i => … }

flatMap() が必要なパターン。 Generator が複数あり、yield で値を返す時。

for (i <- obj1; j <- obj2) yield { … }
// 書き換えると
obj1.flatMap { case i => for (j <- obj2) yield { … } }
// obj1 には flatMap が必要。obj2 には map が必要。

withFilter() が必要なパターン。 Guard があるとき。

for (i <- obj1 if i < 0) { … }
// 書き換えると
obj1.withFilter(i => i < 0).foreach { case i => … }

こんな感じで書き換えできるので、本質的には for 式要らないんだと思う。 でも複数の generator を回す時とか、foreachmap, flatMap で書くとネストが深くなってしまうので、for文だとスッキリかけて嬉しいってのはある。

繰り返し以外の文脈

必要なメソッドさえ揃っていればいいので、繰り返し以外の文脈で用いることができる。

たとえば scala.Option はオプショナルな値を表す。 値があるかもしれないし無いかもしれないという文脈であり、Javaでいえば null を使いたく場面で使う型である。 Option では for 式に関連するメソッドが、値がある時には関数を実行し、そうでなければ何もせず値なしを結果とするという意味合いで定義されている。

for (i <- Some(1); j <- Some(2)) yield i + j // Some(3)
for (i <- Some(1); j <- None) yield i + j    // None

上記のように「Option 型の値がいくつかあり、全ての値が存在している時に何かする」というのが for 文で実現できる。

他にも scala.concurrent.Future は将来的に得られる値を表現する型で、 for式関連のメソッドは値が得られた時に関数を実行するように定義されている。

val f1: Future[Int] = …
val f2: Future[Int] = …
val f3: Future[Int] = for (v1 <- f1; v2 <- f2) yield v1 + v2

ここでは f1, f2 の値が将来得られたら v1 + v2 を計算するという、コールバックの登録的な意味合いを持っている。

scala-arm では、最後にリソースを開放するという意味合いを for 式関連メソッドに持たせることで、for 式の最後でのリソース解放を実現している。

まとめ

foreach, map, flatMap, withFilter というメソッドの定義次第で for 式の使い道は無限大。

Comments