2017年5月13日土曜日

Java 8 ラムダ式の初歩(続き1)

 前回の続編として、ラムダ式(Lambda Expressions)の続きを検討してみます。
 下図は、自分が最も関心のあるキーワード(一つだけ)を含むタイトルの書籍を何冊持っているかを示しています。そのキーワード毎に、学生が持っている書籍数を合計するカウントプログラムを考えます。

最も関心の高いキーワードを含む書籍を何冊持っているか?

 やりたいことは自明であり、とにかくプログラムを作ることはできます。しかし、ここでは、書籍のカウントの仕方を問題にします。よくあるプログラムでは、カウントするためのメソッド(関数)の内容は、「指定されたものをカウント(合計)する」のと「カウントすべきものは何か、それをどのような手順で決めるか」が混っていることがあります。

 ここでは、後者の「カウントされるものが何か(どの本か)」を、前者の「カウントすること」から分離させることを考えます。それによって、書籍の種類を追加したり、合計したい本に何らかの条件を付けたりしても、前者の「「指定されたものをカウント(合計)する」関数は変更を受けないはずです。これは古くからあるデザインパタンの一つ(関心の分離:seperation of concernsですが、ラムダ式を用いることによって、いっそう柔軟な実装ができます。

 以下のプログラムは、Venkat Subramaniam氏の書籍[1]を参考に作りました。このなかで、【方式1】は、氏の方式に沿ったものです。一方、【方式2】は、当方で独自に修正してみたものです。【方式1】では、カウント関数 countAを呼び出す際に、「何をカウントすべきか」を決める手続きをラムダ式で(t -> t.type == Type.Keras のように)渡している所に特徴があります。

 一方、【方式2】は、関数呼び出しの度に同じような形のラムダ式を与えたくないので、ラムダ式自体を分離してみました。しかし、それは、カウントする関数 countBの中に含まれているので、「関心の分離」からは好ましくないようです。結論として、【方式1】に従うのがよさそうです。ただし、【方式2】もラムダ式の活用練習にはなるでしょう。

========================================
package designing.fpij;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import designing.fpij.Student.Type;
public class StudentManager2 {
  
  // 【方式1】Venkat Subramaniamの書籍の方式(但し、以下のコードは山本自作)
  public static int countA(List<Student> sts, Predicate<Student> selector){
  return sts.stream().filter(selector).mapToInt(s -> s.number).sum();
  }
  // 【方式2】Venkat Subramaniamの方式の変更版(以下のコードは山本自作)
  public static int countB(List<Student> sts, Type type){
    Predicate<Student> selector = s -> type == null ? true: s.type == type;
    return sts.stream().filter(selector).mapToInt(s -> s.number).sum();
  }

  public static void main(final String[] args) {
    List<Student> students = Arrays.asList(
    new Student(Type.Java8, 3), new Student(Type.Python, 1),
    new Student(Type.NetLogo, 1), new Student(Type.Python, 2)
    );
    
    // 【方式1】を使った実行
    System.out.println("total:" + countA(students, t->true));
    System.out.println("Keras:" + countA(students, t->t.type==Type.Keras));
    System.out.println("Python:" + countA(students, t->t.type==Type.Python));    

    // 【方式2】を使った実行
    System.out.println("total:" + countB(students, null));
    System.out.println("Keras:" + countB(students, Type.Keras));
    System.out.println("Python:" + countB(students, Type.Python));
  }
}

class Student {
    public enum Type {Java8, Python, NetLogo, Keras }; 
    protected Type type;
    protected int number;
    public Student(Type type, int number) {
        this.type = type;
        this.number = number;
    }
}
========================================
実行結果:【方式1】,【方式2】とも同一
total:7
Keras:0
Python:3
========================================

[参考文献]
[1] Venkat Subramaniam : Functional Programming in Java, O'Reilly, 2014.
(O'Reilly Japanから、和訳本も出版されている)

0 件のコメント:

コメントを投稿