【前置き】
Java8から関数型インターフェースが使用可能になりました。
具体的に「ラムダ式」「Stream」「Optional」「Files」と言った方がわかりやすいでしょうか。
関数型インターフェースの使用を半ば強制されるフレームワークが登場していたり(例:Apache Spark)、関数型インターフェースでJavaを書く開発者も増えてきたので、目にすることも多くなってきたかと思います。
関数型インターフェースは関数型プログラミングをサポートするものであるため、従来からJavaでサポートされていたオブジェクト指向プログラミングとは発想が異なります。
そのため、従来のJavaを学習してきた方にとっては抵抗感を感じるものであると思います。
今回の記事では、抵抗感を少しでも減らすために、関数型プログラミングの考え方を簡単に紹介したいと思います。
【サンプルコード】
言葉で説明するよりも先にサンプルコードを見た方がわかりやすいと思うので、サンプルコードを先に紹介します。
年齢のリストから30代の人数を数える、というプログラムです。
ごく短いプログラムですので、お付き合いください。
・FunctionTest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import java.util.Arrays; import java.util.List; public class FunctionalTest { public static void main(String[] args) { Integer[] age = {38, 26, 40, 32, 36}; List<Integer> list = Arrays.asList(age); System.out.println("手続き型として記述"); int count = 0; for (int i = 0; i < list.size(); i++) { int individualAge = list.get(i); if (individualAge >= 30 && individualAge <= 39) { count++; } } System.out.println(count); System.out.println("関数型インターフェースを使用"); System.out.println(list.stream() .filter(x -> x >= 30 && x <= 39) .count()); } } |
・実行結果
1 2 3 4 |
手続き型として記述 3 関数型インターフェースを使用 3 |
【関数型プログラミングの考え方】
関数型プログラミングでは、以下のことを実現しようとしています。
色々難しい用語(例えば「副作用」等)はあるのですが、今回は用語を使わずに簡潔にまとめます。
・内部状態(State)を排除する
最も本質的な考え方です。
関数型プログラミングでは、内部状態を排除することを目的としています。
「内部状態」とは、上記のコードで言うと「count」や「i」を指します。
内部状態が入りこんでしまうと、内部状態により関数の結果が変わってしまうため、内部状態を把握する必要が出てきてしまい、可読性が悪化します。
(把握のために「count」や「i」をトレースする必要が出てきてしまう)
把握しきれずに意図しないバグを出してしまうことも珍しくありません。
内部状態を排除して、品質を上げよう、という発想です。
また、コンピュータにとっては内部状態は重要ですが、人間にとってはやりたいことを実現できれば良く、内部状態は重要ではありません。
重要ではない記述を削減することでコードを完結にしたい、という発想もあります。
Java8のラムダ式では、ラムダ式の外部で定義された変数の値をラムダ式の内部で変更することを禁止されています(コンパイルエラーになる)。
その背景には、内部状態の排除があると思っています。
・自然言語に近い形で処理を記述する
これは、コードが簡潔になった結果生じた副次的な考え方かもしれません。
関数型プログラミングでは、関数を組み合わせることにより処理を実現します。
関数を次々とつなぎ合わせるように記述することで、ソースコードが自然言語に近い形になります。
わかりやすく言えば、ソースコード自体がコメントのようになります。
例えば、サンプルコードでは「年齢のリストから30代の人数を数える」という処理を行おうとしています。
従来のプログラミングでは、これを実現するためにforループとかカウント用の変数を使用しており、何をしているのか把握するためには、内部状態をトレースして意図を汲み取る必要があります。
しかし、関数型プログラミングでは、
「list.stream().filter(x -> x >= 30 && x <= 39).count()」→
「listを30<=x<=39でfilterしてcountする」
と読めるため、
「年齢のリストから30代の人数を数える」
という処理であることを自然に把握することができます。
いかがでしたでしょうか。
IT業界、特にSIer業界だと、情報処理技術者試験を軸にして知識を身に付けることが多いかと思います。
しかし、情報処理技術者試験では手続き型プログラミングやオブジェクト指向プログラミングを中心とした出題で、関数型プログラミングが扱われることは全く言って良いほどありません。
そのためとっつきにくさは拭えないと思いますが、先進的な企業を中心に関数型プログラミングを取り入れる企業も出てきています。
これからのことを考えると、せめて、関数型プログラミングに対する抵抗感は払拭するべきではないかと思っています。
今回はこれで締めくくりたいと思います。
では、また来週!
コメント