Java : 高階関数

当初の目的とはだいぶ違う方向に進んでいってしまいましたが、そのおかげでだいぶJavaという言語に対する知見を深めることができたような気がします。
ということで、そろそろJava高階関数を書くテーマに挑戦できそうです。

      • -

まず、ここで考える高階関数として、配列に対する次の3つの関数を挙げます。

collect
配列の各要素に対して操作を加え、その結果を別の配列として返す。
filter
配列の各要素に対してテストを行い、結果が真となったものだけからなる新しい配列を返す。
fold
配列の各要素に対する操作の結果を蓄積する。

この3つは、SICPでは、"2.2.3 Sequences as Conventional Interfaces"のところで、map, filter, accumulateとして登場する関数であり、Smalltalkでは、Collectionクラスの、collect:, select:, inject:into:というメソッドに対応します。(mapを使わず、collectとしたのは、JavaにはMapというコレクションインターフェースが存在するので、シンボルを別にしたかったためです)
ここでは、String型の配列のみを対象として扱います。これは、JDK1.5より導入されたGenericsを使うことで容易に一般化することができます。また、コレクションクラスへの適用も容易です。
ただし、基本型については、それぞれについて別のクラスを作成する必要があります。(追記)JDK1.5よりautoboxingが導入されたので、基本型への拡張も容易です。

      • -

foldについては、左結合と右結合の2種類があります。Lispを始めとしたconsを基本的なデータ構造として持つ言語では、右結合のfoldが非常に有益な効果をもたらすのですが、Javaにはそういう事情は特にないので、ここでは左結合のfoldのみを対象とします。

collect

public abstract class Collect {
  protected abstract String each(String s);
  public String[] all(String[] sa) {
    String[] ret = new String[sa.length];
    for (int i=0; i<sa.length; ++i) {
      ret[i] = each(sa[i]);
    }
    return ret;
  }
}

filter

publid abstract class Filter {
  protected abstract boolean each(String s);
  publid String[] all(String[] sa) {
    String[] tmp = new String[sa.length];
    int j=0;
    for (int i=0; i<sa.length; ++i) {
      if (each(sa[i])) {
        tmp[j++] = sa[i];
      }
    }
    String[] ret = new String[j];
    for (int i=0; i<j; ++i) {
      ret[i] = tmp[i];
    }
    return ret;
  }
}

fold

public abstract class Fold {
  protected abstract String each(String s0, String s1);
  public String all(String ret, String[] sa) {
    for (int i=0; i<sa.length; ++i) {
      ret = each(ret, sa[i]);
    }
    return ret;
  }
}

使用例

「長さが5以上ある文字列だけを取り出し、小文字を大文字に変換して、全て結合して一つの文字列として返す」という処理を書いて見ます。

class Main {
  String join_Uppper_of_moreThan_5(String[] input) {
    return
      new Fold()
        { String each(String s0, String s1) { return s0+s1;} }
      .all( "",
           new Collect()
              { String each(String s) { return s.toUpperCase();} }
            .all( new Filter()
                    { boolean each(String s) { return s.length() >= 5;} }
                  .all(input)));
  }
}

まあまあかな? 静的型付けで型推論がないことを考えると、いい線行っているんじゃないでしょうか?
ところで、よくよく見ると、この方式は、デフォルトでカリー化しています。だから、こういう風に書いてもよい。

class Main {
  String join_Uppper_of_moreThan_5(String[] input) {
    Fold join = new Fold()
      { String each(String s0, String s1) { return s0+s1;} }
    Collect upper = new Collect()
      { String each(String s) { return s.toUpperCase();} }
    Filter moreThan_5 = new Filter()
      { boolean each(String s) { return s.length() >= 5;} }
    
    return join.all( "", upper.all( moreThan_5.all( input )));
  }
}