コレクションと関数による関数型プログラミングの真価


入力をどう変化させるか?

命令型のプログラミングでは、制御(命令)フローを考え、そのフローにてデータをどのように変化させるかを考えますが、Scala では入力を出力に変換する という考え方にてロジックを組み立てていきます。


奇数を抽出する場合、命令型の考え方では、入力値をループにて回し、各要素について奇数かどうかを判定し、抽出結果に追加していく・・ のように命令フローとデータの状態について合わせて考えていく必要があります。


Saclaでは、入力値をどのように変換すれば出力値が得れれるかを考えます。奇数を抽出する場合、入力のリストを 奇数かどうか の条件でフィルターをかければ結果が得られます。

object Sample extends Application {
  val s = List(1,2,3,4,5)
  println(s.filter(x => x % 2 ==1))
  println(s)
}

出力結果

List(1, 3, 5)
List(1, 2, 3, 4, 5)

奇数が抽出されました。ここで重要な点が2つあります。

  • プログラミングの過程で、奇数抽出の条件のみを焦点とすればよい
  • 入力元となった s は初期値のまま変化がない

関数型プログラミングでは、入力を出力に変換するロジックに集中でき、かつその変換による副作用が無いことが特徴で、堅牢なプログラミングが可能となる理由です。

remove

remove は filter とは逆の働きをします。

List(1,2,3,4,5).remove(x => x % 2 ==1)

出力結果

List(2, 4)

奇数が抽出されました。

関数を引数として渡す

filter の引数には以下のようにisOddという関数を渡すことで読み下し易くすることもできます。

List(1,2,3,4,5).filter(isOdd)
def isOdd(x: Int) = x % 2 == 1

takeWhile

takeWhile はリストの要素先頭から見ていき、条件を満たさなくなるまでのリストを返却します。

val l = "across the universe".toList.takeWhile(c => c!=' ')
println(l)

空白が現れるまでの内容がリストとして得られます。

List(a, c, r, o, s, s)

map

map はリストの各要素を指定の方法で変換します。

val l = List(2,4,6).map(n => n / 2)
println(l)

出力結果

List(1, 2, 3)


文字列を単語に分割して、単語の数を数える

val s = "across the universe".split(' ').toList.map(_.length)
println(s)

出力結果

List(6, 3, 8)

sort

その名の通り、sort。

val s = "across the universe".split(' ').toList.sort(_ > _)
println(s)

出力結果

List(universe, the, across)

reduceLeft

reduceLeft はコレクションに対する操作を逐次的に行います。

val l = List(2,10,4,52,6).reduceLeft(_ max _)
println(l)

上記例では、List の要素の 2 と 10 を引数に関数を呼び出します。ここでは関数は max なので、大きい値の 10 が返却されます。次にこの結果の 10 と 4 を引数に max 関数が呼び出されていき、最終的にList中の最大値が得られます。

52

foldLeft

foldLeft は reduceLeft と同様ですが、初期値を与えられる点がことなります。

val l = List(1,2,3).foldLeft(10)(_ + _)

上記は初期値を10として、10 + 1 + 2 + 3 を計算し、16が結果として得られます。

まとめ

Scala では入力元となるデータを用意し、そのデータをどのように変換したら出力が得られるか?を考えます。命令型プログラミングで考える制御構造やループについては考えないのです。つまり、変換のロジックのみに集中でき、副作用についても心配しなくてよくなるのです。