LoginSignup
22

More than 5 years have passed since last update.

Javaのラムダ使うAPIとClojureをちょこっと比較してみる

Posted at

ちょこっとじゃない??

JavaのStreamとClojureはわりと似たような感じで書けたりします

range_filter_map_forEach.java
IntStream.range(1, 10)
        .filter(i -> i % 2 == 0)
        .map(i -> i * 3)
        .forEach(System.out::println);
range_filter_map_doseq.clj
(->> (range 1 10)
     (filter #(zero? (rem % 2)))
     (map #(* % 3))
     (#(doseq [i %] (println i))))
skip_limit_reduce.java
IntStream.range(1, 10)
        .skip(3)
        .limit(5)
        .reduce((i, j) -> i * j)
        .ifPresent(System.out::println);
drop_take_reduce.clj
(->> (range 1 10)
     (drop 3)
     (take 5)
     (reduce *)
     println)

わりと似てる・・・
以降ではクラスごとにどんな機能があるか見てみます

java.util.stream.Stream関連

Streamクラス、IntStreamクラス等の機能です

機能 Java Clojure
範囲(endを含まない) range range
範囲(endを含む) rangeClosed (range (inc x))
条件がtrueのものを残す filter filter
条件がfalseのものを残す filter((...).negate()) remove
関数適用した結果に変換 map map
関数適用した結果がnullのものを除外するmap map(...).filter(x -> x != null) keep
適用する関数の入力にindexがあるmap,keep map-indexed,keep-indexed
関数適用した結果のネスト構造を1つ解くmap flatMap mapcat
副作用を伴うループ実行 forEach doseq
畳み込み処理 reduce reduce
指定した最大件数まで取得(先頭から) limit take
指定した件数を破棄(先頭から) skip drop
指定件数を最後から取得、破棄 take-last,drop-last
指定条件を満たす要素が現れるまで取得、破棄 take-while,drop-while
条件を満たすものが1つでもあるか anyMatch some
すべて条件を満たすか allMatch every?
1つも条件を満たさないか noneMatch not-any?
少なくとも1つ条件を満たさないものがあるか !(... allMatch(...)) not-every?
1番目の要素を取得 findFirst first
2番目、最後、任意の箇所の要素を取得 second,last,nth
副作用処理を挟み込む peek #(do (...) %)
重複要素を排除 distinct distinct
ソート sorted sort
関数を繰り返し適用して要素生成 iterate iterate
繰り返し要素生成 generate repeatedly
リストを繰り返して要素生成 cycle
連結 concat concat
和を取得 sum apply +
最小の要素を取得 min min
最大の要素を取得 max max
要素数を取得 count count
算術平均を取得 average (fn [x] (/ (apply + x) (count x)))

Clojureのmapは複数のシーケンスを並行して扱うことができます。また、reduceは途中で止められます。

Clojureのcycle関数は、Javaにすると以下のような感じでしょうか

Cycle.java
static <T> Stream<T> cycle(T t0, T... ts){
    return Stream.generate(new Supplier<T>() {
        int index = -1;
        @Override
        public T get() {
            index = (index + 1) % (ts.length + 1);
            if(index == 0){
                return t0;
            }
            return ts[index - 1];
        }
    });
}

他に複数のStreamを並行して1つにまとめるいわゆるzip関数と、Streamのcloneを作れる関数がセットで欲しいところでしょうか

java.util.stream.Collectors関連

他に統計値(IntSummaryStatisticsなど)を取得できる便利な機能もあります

機能 Java Clojure
文字列の(区切り文字付)連結 joining clojure.string/join
(条件指定して)最小の要素取得 minBy min-key
(条件指定して)最大の要素取得 maxBy max-key
指定分類でグルーピング groupingBy group-by
条件の適用結果(true/false)でグルーピング partitioningBy group-by

java.util.List関連

Javaの方はミュータブルな(Listの中身が変わる)処理で、Clojureの方はイミュータブルな処理です

機能 Java Clojure
条件に合致したものを削除 removeIf remove
関数適用した結果に変換 replaceAll map

java.util.Map関連

Listと同様にJavaの方はミュータブル

機能 Java Clojure
指定したキーと対応する値に関数適用して値を変換する compute
指定したキーが存在しない場合、computeを行う computeIfAbsent
指定したキーが存在する場合、computeを行う computeIfPresent
Mapのすべての値を関数適用した結果に変換 replaceAll
新しい値と指定したキーの値を(関数適用しつつ)マージする merge update-in
MapとMapを(関数適用しつつ)マージする merge-with

※ClojureのmergeはJavaではputAllとほぼ同じ

JavaのreplaceAllをClojureで実現するとこんな感じでしょうか

map-vals-with-key.clj
(defn map-vals-with-key [f hmap]
  (reduce (fn [hmap [k v]] (assoc hmap k (f k v))) hmap hmap))

また、Clojureのmerge-withをJavaで実現するとこんな感じ?

MergeWith.java
public static <K, V> Map<K, V> mergeWith(Map<K, V> m1, Map<K, V> m2, BinaryOperator<V> f){
    m2.forEach((k2, v2) -> {
        m1.computeIfPresent(k2, (k1, v1) -> f.apply(v1, v2));
        m1.putIfAbsent(k2, v2);
    });
    return m1;
}

その他

Streamを取得できるメソッドとして便利なもの

機能 Java Clojure
行のStream java.io.BufferedReader#lines line-seq
文字のStream CharSequence#chars (seq "abc")
文字コードポイントのStream CharSequence#codePoints

文字のStreamはIntStreamになっています。サロゲートペアが2つのintになるのがcharsの方です

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22