カテゴリー別アーカイブ: 書評

Javaによる関数型プログラミング

@skrbより献本いただいた「Javaによる関数型プログラミング」を、ちまちまと通勤時間などを利用して読了しました。

今年はどのJava関連の勉強会に行っても「ラムダ式とStream APIで関数型プログラミングで安心安全!」という話がされていて耳タコな人も多いのではないでしょうか。

とは言え勉強会で聞くのは関数型プログラミングの”さわり”だけの場合がほとんどです。本書はそんな関数型プログラミングの”さわり”から、後半は実践的な例までひと通り書かれているため、本書を読んでおけばJava8での開発にも、とりあえず臆することはなくなるはずです。

関数型プログラミングの本質は1章の「まるで要求仕様を述べるようにロジックが流れていきます」という一文に込められています。入力と出力を関数(function)によって簡潔に記述し、連鎖することで目的の値を求める数学的なアプローチと言えるでしょう。

それに対して命令形プログラミングはメソッド(Method)によるデータ構造の破壊的変更や副作用を伴う、叙述的で「まるで小説のような」ロジックと言えます。

我々エンジニアは小説のようなプログラムを書くのではなく、仕様を明確かつ簡潔に書くことを善とします。本書で得られる関数型プログラミングの考え方は、Java8に限らず他の関数型言語への足掛けとして、将来のプログラミングライフを間違いなく豊かにしてくれるでしょう。

ScalaやErlang、LispやHaskellといった関数型言語がここ数年のトレンドですが、Java界隈での関数型プログラミングのアプローチはJava8で突然始まったわけではなく、Guavaのコレクションライブラリなどでそのエッセンスを感じとった人も多いのではないでしょうか。Guavaを触ったことがあれば、4章まではスラスラと頭に入ってくると思います。

ラムダ式の応用として5章からは非常に参考になります。Execute around methodパターンなどはJava7までは匿名クラスを使った非常に冗長な書き方をしていましたが、Java8からはラムダ式のお陰で非常にすっきりと記述することができます。

以前JJUGナイトセミナーで紹介したJPAのCriteriaを生成するExecute around methodパターンも、

これが

このように簡潔に記述ができるようなります。EclipseやIntelliJ IDEA、NetBeansなど最新のIDEではラムダを適用できるコードは自動的に変換する機能があるので、はじめはIDEの力を借りて既存のコードを修正しながらラムダ式適用の勘を掴んでいくのもいいでしょう。

7章は前半の末尾再帰/末尾呼び出し最適化に関する説明が分かりづらくて、初めて末尾再帰を理解するには少々ツラそうです。「再帰が深いとスタックを喰い潰す通常の再帰を、関数の末尾で再帰呼び出しするコードを単純なループに展開する最適化」みたいな一般的な末尾再帰呼び出し最適化が何を目的としているのか書いてあるとよかったのかなと思います。このあたりが比較的分りやすいかと思うので7章を読む前に目を通しておくといいかもしれません。

8章の「8.2.2 命令形スタイルからの脱却」では、メソッドの実装を整理する際にやってしまいがちな例が書かれています。

    final List<StockInfo> stocks = new ArrayList<>();
    for(String symbol : Tickers.symbols) {
      stocks.add(StockUtil.getPrice(symbol));
    }
    
    final List<StockInfo> stocksPricedUnder500 = new ArrayList<>();
    final Predicate<StockInfo> isPriceLessThan500 = StockUtil.isPriceLessThan(500);
    for(StockInfo stock : stocks) {
      if(isPriceLessThan500.test(stock))
        stocksPricedUnder500.add(stock);
    }
    
    StockInfo highPriced = new StockInfo("", BigDecimal.ZERO);
    for(StockInfo stock : stocksPricedUnder500) {
      highPriced = StockUtil.pickHigh(highPriced, stock);
    }
    
    System.out.println("High priced under $500 is " + highPriced);

このような3つのforループを

    StockInfo highPriced = new StockInfo("", BigDecimal.ZERO);
    final Predicate<StockInfo> isPriceLessThan500 = StockUtil.isPriceLessThan(500);

    for(String symbol : Tickers.symbols) {
      StockInfo stockInfo = StockUtil.getPrice(symbol);
      
      if(isPriceLessThan500.test(stockInfo))
        highPriced = StockUtil.pickHigh(highPriced, stockInfo);
    }
    System.out.println("High priced under $500 is " + highPriced);

1つのforループにまとめています。これを本書では「得たものと失ったものがあります」と表現しています。それはなんでしょうか?

ぱっと答えが浮かぶ方は本書を読まなくても大丈夫かもしれません。関数型プログラミングを適用することで、上記どちらよりもエレガントなコードに生まれ変わります。自信が無い方はぜひ本書を手にとって確認してみてください。