はじめに
関数型言語って何...?て人のためにJavaと比較をしたりしながら、どんなものなのかを知ってもらおうと思い本記事を書かせてもらいました。皆さんの学びの助けになれば幸いです。
関数型言語とは
関数がそのままプログラムになる宣言型言語でλ計算を基礎としています。このλ(ラムダ)が言わずもがなJavaのラムダ式に繋がってきます。興味が湧いたら是非最後まで読んでいってください。
- 宣言型言語とは
- 手続きを明示的に記述せず、データ間の関係を定義することに重点をおき、アルゴリズムを記述する言語
手続き型と関数型の違い
- 手続き型
- 代表的な言語にC言語・Fortran・COBOLなどがあります。コンピュータに逐次的に実行させる動作を人間が命令でひとつひとつ丁寧に書いていかなければなりません。例えるならば、「水を飲む」という動作をさせたいとき、「右手をあげて、コップがある方向を向いて、右手を15cm前に進めて、右手の親指の第一関節を曲げて...」みたいに細かい処理まですべて人間がコンピュータに指示してあげなければなりません。単に「水を飲んで」と言ってもコンピュータが理解できずダメなのです。
- 関数型
- 代表的な言語にHaskell・Scala・Standard MLなどがあります。人間は細かな手続きを書く必要がありません。細かい部分はコンピュータがやってくれます。手続き型の例で出した「水を飲む」という動作をさせたいとき、「水を飲んで」と言えばコンピュータは理解してくれるのです。
int sum = 0
for(int i = 1; i <= 10; i++){
sum += i;
}
fun sum n =
if n = 1 then 1
else n + sum(n-1)
sum 10
この単純なプログラムでも大分書き方が違うのがわかります。Javaで書いた方は0に初期化した変数sumを用意して、for文で1から10の数値を逐次足していくというプロセスを踏んでいます。
それに対して、関数型言語のStandard ML(SML)で書いた方は、シグマ計算の第一項は1になり、第n項のときはnに$\sum^{n-1}_{k=1}k$を足したものになるという風な書き方です。今回は簡単な計算でしたので少し良さがわかりにくかったかもしれませんが、関数型言語の方がシグマ計算のアルゴリズムを直感的に書いていると思いませんか?
また、関数型言語の特徴として、関数を引数にとることができるというものがあるのでそれも少し見てみましょう。
以下はnの3乗の総和を計算するプログラムです。
$$\sum^{10}_{k=1}k^3=1^3+2^3+3^3+\dots+10^3$$
fun power m n =
if m = 0 then 1
else n * power (m-1) n;
val threeP = power 3;
sum threeP 10
$n^0$のときは1を返し、それ以外のときは$n$に$n^{m-1}$をかけるという関数powerを用意します。次にthreePという変数を用意し、そこにpower 3を束縛します。これでこの変数は$n^3$を計算できるようになりました。
そして、sum threeP 10で$n^3$の総和を$n=10$まで求めます。
細かい内容は理解できなくても大丈夫です。関数型言語では直感的な書き方ができるんだな~てことだけでも知っておいてください。
関数型プログラミングの特徴
- 宣言的な記述
- 前の節で書いたように、アルゴリズムを直感的な書き方で表現できます。
- 変数の非破壊的代入
- 値を束縛された後は値は変化しないという特徴。以下のように書くと変数numberは10という値に束縛され、書き換えられることはありません。対して、C言語などの手続き型の言語では、破壊的代入が可能で、つまり値の上書きができてしまいます。
var number = 10;
- 参照透過性
- 同じ引数で呼び出された関数は常に同じ値を返す。あるときは1を返して、あるときは10を返すみたいなことはありません。同じ引数ならば、必ず同じ値を返してくれます。
- 副作用を(極力)起こさない
- 戻り値を返す以外の作用は起こしません。例えば、x=10で変数xに10という値が代入されると、それは式の評価による副作用となります。
Javaで関数型言語を使う
さて、ここまで来て「いや私は関数型言語なんて使わんし...」って思った方、ちょっと待ってください。実は関数型言語はJavaやPythonといった、主要なプログラミング言語においても使えるようになっているんです。Java8よりラムダ式という記述方法が使えるようになっていますが、まさにこれが関数型言語の書き方をJavaでも使えるようにしたものなんです。
百聞は一見に如かず、ということで早速Javaで手続き的に書いたプログラムと、ラムダ式を使ったプログラムを比較してみましょう。
以下はマイナスの数値を抽出してコンソールに出力するプログラムです。
int[] numbers = {-1,4,-3,10,15,-2,18};
ArrayList<Integer> list = new ArrayList<>();
for(int num : numbers){
if(num < 0) list.add(num);
}
for(int num : list){
System.out.println(num);
}
int[] numbers = {-1,4,-3,10,15,-2,18};
Arrays.stream(numbers).filter(num -> num < 0).forEach(System.out::println);
なんとここまでコードがすっきりします。どちらも-1,-3,-2がコンソールに表示されます。
num -> num < 0の部分が「マイナスの数値を抽出して」というアルゴリズムに該当します。
まとめ
関数型言語のもととなっているラムダ計算は、かなり昔に考えられたものです。しかし、近年JavaやPythonで関数型言語の書き方が使えるようになるなど、注目を集めています。少しでもこの記事で関数型言語についての理解の助けになれば幸いです。