10.3 For 内包表記の変換

すべての for 内包表記は3つの高階関数 map, flatMap, filter で表現できます。これが翻訳のスキーム(枠組み)であり、Scala コンパイラも使っています。

• 単純な for 内包表記

for (x <- e) yield e'

これは次のように翻訳されます。

e.map(x => e')

• for 内包表記

for (x <- e if f; s) yield e'

ただし f はフィルタで、s は (空でも良い) ジェネレータあるいはフィルタの列。これは次のように翻訳されます。

for (x <- e.filter(x => f); s) yield e'

そして後者の式に対して翻訳が続きます。

• for 内包表記

for (x <- e; y <- e'; s) yield e''

ただし s は (空でも良い) ジェネレータあるいはフィルタの列。これは次のように翻訳されます。

e.flatMap(x => for (y <- e'; s) yield e'')

そして後者の式に対して翻訳が続きます。

たとえば、「和が素数になる整数の組」を例にとると、

for { i <- range(1, n) j <- range(1, i) if isPrime(i+j) } yield {i, j}

この式を翻訳すると次が得られます。

range(1, n) .flatMap(i => range(1, i) .filter(j => isPrime(i+j)) .map(j => (i, j)))

逆に、関数 map, flatMap, filter を for 内包表記で表すこともできます。3つの関数を今度は for 内包表記を使って実装してみます。

object Demo { def map[A, B](xs: List[A], f: A => B): List[B] = for (x <- xs) yield f(x) def flatMap[A, B](xs: List[A], f: A => List[B]): List[B] = for (x <- xs; y <- f(x)) yield y def filter[A](xs: List[A], p: A => Boolean): List[A] = for (x <- xs if p(x)) yield x }

驚くことではありませんが、Demo.map の本体の for 内包表記の翻訳は、クラス List の map を呼び出します。同様に、Demo.flatMap と Demo.filter は、クラス List の flatMap と filterの呼び出しへ翻訳されます。

演習 10.3.1 次の関数を for を用いて定義しなさい。

def flatten[A](xss: List[List[A]]): List[A] = (xss :\ (Nil: List[A])) ((xs, ys) => xs ::: ys)

演習 10.3.2 次を高階関数を用いて翻訳しなさい。

for (b <- books; a <- b.authors if a startsWith "Bird") yield b.title for (b <- books if (b.title indexOf "Program") >= 0) yield b.title