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