Pattern 1. Replacing Functional Interface

"Functional Programming Patterns in Scala and Clojure"
という本を読んでいます。
内容は「オブジェクト指向のコードを関数型プログラムに置き換えるとどうなるか」
というのを、パターン形式で記述してあります。
英語は苦手だし Java は大学以降やっていないので内容を正しく把握できていないところもあるかもですが、内容をまとめてみたいと思います。
Clojure まで手を付けると読み終わらなさそうなので Scala だけを(^^;)

1 つ目のパターンは Replacing Functional Interface。訳すと「関数型インターフェースの置き換え」。

前提知識

Functional Interface とは

メソッドが 1 つだけ定義された interface。
Runnable インターフェース、Callable インターフェースなど。
wikipedia:関数オブジェクト の "Java における関数オブジェクト" が詳しい。

FP(Scala) だとどう変わるか

  • Functional Interface が不要になる。
  • Functional Interface のクライアントのコードが短くなる。
    • Functional Interface のクライアントコードは下記のような実装がよくされる
      • 1. 匿名クラスを作成
      • 2. 抽象メソッドを宣言
      • 3. 実装
    • これらのうち、1.、2. が不要になる
    • 例A
Collections.sort(hoge, new Comparator() {  ←★ 1.
    public int compare(Hoge h1, Hoge h2) {       ←★ 2.
        return h1.getValue() < h2.getValue();    ←★ 3.
    }
});

どういう方法か

  • 高階関数を使う
    • Functional Interface の代わりに関数を渡す。例A は下記のように書き換わる(イメージ)
Collections.sort(hoge, (h1, h2) => return h1.getId().compareTo(h2.getId()))
↑ 第 2 引数に "引数が 2 つで戻り値が boolean の関数" を渡す
↑ あくまでイメージなのでコンパイル通りません...
    • ラムダ式を名前付き関数にしてみると次のようになる
def complicatedSort(h1: Hoge, h2: Hoge) = h1.getValue() > h2.getValue()
Collections.sort(hoge, complicatedSort)
↑ Java だと、Comparator インターフェースを implements したクラスを作成して、compare メソッドを実装する必要があるが、
↑ 高階関数だと適切な引数と戻り値を持ったメソッドを定義するだけでよい。

OOP と FP の比較

常に匿名クラス、無名関数で実装するなら、Functional Interface にする意味はあまりなさそう。
名前付きクラス、名前付き関数の場合、高階関数で実装すると、引数と戻り値が同じだったらどんな関数でも渡せるという柔軟性がある。
一方で、Functional Interface を使う方が実装の意図は伝わりやすいかもしれない。Comparator を implements してたら比較用のメソッドと分かるし、メソッド名も必ず compare にしないといけないと制限できる。
クライアント側に制約をかけるという目的があるなら Functional Interface を使うのもありかもしれない。あんまりそういうケースはない気がするけど。