Java Advent Calendar 2016の16日目の記事です。
※追記: Javaslang
は、名称がVavr
に変更になりました
Javaで関数型プログラミングを行うためのライブラリとしてJavaslang
が存在しています。
Javaslang
は、Scalaの長所をJava言語にできる限り導入することを目的としています。
関数型プログラミング用のデータ構造や制御構造を提供してくれているようです。
関数型言語もモナドも詳しく理解していない私がとりあえず触ってみました。
利用準備
pom.xmlに以下を追加します。(今回のエントリを書くにあたってはバージョン2.1.0-alphaを使用しました)
<dependency> <groupId>io.javaslang</groupId> <artifactId>javaslang</artifactId> <version>{javaslang-version}</version> </dependency>
Javaslangを使うにあたってjavaslang.API
をstatic importをしておくと何かと便利なメソッドが利用できるので、
基本的にどのサンプル試す場合も以下のimportは行っているものとします。
import static javaslang.API.*;
関数型コレクション
関数型の各種操作を提供するためのコレクションのクラス群が実装されています。
Javaslangで提供されるコレクションは、Traversable
インターフェースを継承しており多数の操作を提供しています。
(基本的にどのコレクションでも共通的な操作ができるようになっています)
List / Set / Map
リテラル表現で生成
リテラル表現でのコレクションの生成を行うことができます。
■サンプルコード
import javaslang.collection.*; import static javaslang.API.*; // List List<String> list1 = List.of("taro", "jiro", "hanako", "tamako"); println(list1.getClass() + " : " + list1); // 以下のように書くこともできる List<String> list2 = List("aa", "bb", "cc"); println(list2.getClass() + " : " + list2); // Set Set<Integer> set = Set(1, 2, 3, 4, 5); println(set.getClass() + " : " + set); // Map Map<String, Integer> map = Map("key1", 1,"key2", 2,"key3", 3); println(map.getClass() + " : " + map);
■実行結果
class javaslang.collection.List$Cons : List(taro, jiro, hanako, tamako) class javaslang.collection.List$Cons : List(aa, bb, cc) class javaslang.collection.HashSet : HashSet(1, 2, 3, 4, 5) class javaslang.collection.HashMap : HashMap((key1, 1), (key2, 2), (key3, 3))
副作用なし
コレクションへの変更操作を行っても元のインスタンスには変化はなく、新しいコレクションが返されます。(いわゆる副作用がない状態になっています)
■サンプルコード
import javaslang.collection.*; List<String> srcList = List.of("taro", "jiro", "hanako", "tamako"); List<String> newList = srcList.append("makoto").append("minako"); println("srcList: " + srcList); println("newList: " + newList); println("----------"); Set<String> srcSet = Set("taro", "jiro", "hanako", "tamako"); Set<String> newSet = srcSet.replace("hanako", "<unknown>"); println("srcSet: " + srcSet); println("newSet: " + newSet); println("----------"); Map<String, Integer> srcMap = Map("key1", 1,"key2", 2,"key3", 3); Map<String, Integer> newMap = srcMap.remove("key2"); println("srcMap: " + srcMap); println("newMap: " + newMap);
■実行結果
新しいコレクションは要素が変化しているが、元のコレクションは変化していない
srcList: List(taro, jiro, hanako, tamako) newList: List(taro, jiro, hanako, tamako, makoto, minako) ---------- srcSet: HashSet(tamako, taro, hanako, jiro) newSet: HashSet(tamako, taro, <unknown>, jiro) ---------- srcMap: HashMap((key1, 1), (key2, 2), (key3, 3)) newMap: HashMap((key1, 1), (key3, 3))
Stream APIを介さずに色んな操作ができる
Stream APIを介さずにListに対して豊富に提供された各種操作を実行することができます
■サンプルコード
import javaslang.collection.*; import static javaslang.API.*; List<String> srcList = List.of("ayaka", "taro", "jiro", "hanako", "tamako", "makoto", "ayaka"); srcList.filter(s -> s.length() >= 5) // 長さが5以上ものにフィルタリング .distinct() // 重複除外 .map(s -> s.toUpperCase()) // 大文字化 .stdout(); // 各要素を標準出力に出力する println("-----------------"); Set<Integer> set1 = Set(1, 2, 3, 4, 5); Set<Integer> set2 = Set(2, 3, 5, 6); println("set1: " + set1); println("set2: " + set2); // コレクション同士の差分をとる println("set1.diff(set2): " + set1.diff(set2)); println("-----------------"); Map<String, Integer> map = Map("key1", 1,"key2", 2,"key3", -1,"key4", 4); List<String> list = map.takeUntil(tuple2 -> tuple2._2() == -1) .map(tuple2 -> tuple2._1() + "::" + tuple2._2()) .toList(); println("map -> takeUntil -> toList: " + list);
■実行結果
AYAKA HANAKO TAMAKO MAKOTO ----------------- set1: HashSet(1, 2, 3, 4, 5) set2: HashSet(2, 3, 5, 6) set1.diff(set2): HashSet(1, 4) ----------------- map -> takeUntil -> toList: List(key1::1, key2::2)
コレクション同士の比較
eq
メソッドを使うと異なるコレクション同士でも比較ができます
■サンプルコード
import javaslang.collection.*; import static javaslang.API.*; List<String> srcList = List.of("taro", "jiro", "hanako", "tamako"); List<String> newList = srcList.append("makoto").append("minako"); println("srcList: " + srcList); println("newList: " + newList); println("----------"); Set<String> srcSet = Set("taro", "jiro", "hanako", "tamako"); Set<String> newSet = srcSet.replace("hanako", "<unknown>"); println("srcSet: " + srcSet); println("newSet: " + newSet); println("----------"); Map<String, Integer> srcMap = Map("key1", 1,"key2", 2,"key3", 3); Map<String, Integer> newMap = srcMap.remove("key2"); println("srcMap: " + srcMap); println("newMap: " + newMap); println("-- List equals ---------"); List list1 = List(1, 2, 3, 4, 5); List list2 = List(1, 2, 3, 4, 5, 6); List list3 = List(1, 2, 3, 4); println(list1.equals(List(1, 2, 3, 4, 5))); println(list1.equals(list2)); println(list1.equals(list3)); println("-- Set equals ----------"); Set<Integer> set1 = Set(1, 2, 3, 4, 5); Set<Integer> set2 = Set(1, 2, 3, 4, 5, 6); Set<Integer> set3 = Set(1, 2, 3, 4); println(set1.equals(Set(1, 2, 3, 4, 5))); println(set1.equals(set2)); println(set1.equals(set3)); println("-- Map equals ----------"); Map<String, Integer> map1 = Map("key1", 1,"key2", 2,"key3", 3); Map<String, Integer> map2 = Map("key1", 1,"key2", 2,"key3", 3,"key4", 4); Map<String, Integer> map3 = Map("key1", 1,"key2", 2); println(map1.equals(Map("key1", 1,"key2", 2,"key3", 3))); println(map1.equals(map2)); println(map1.equals(map3)); println("-------------------------"); println("list1.equals(set1) -> " + list1.equals(set1)); println("list1.equals(set1) -> " + list1.equals(set2)); println("list1.eq(set1) -> " + list1.eq(set1)); println("list1.eq(set1) -> " + list1.eq(set2));
■実行結果
-- List equals --------- true false false -- Set equals ---------- true false false -- Map equals ---------- true false false ------------------------- list1.equals(set1) -> false list1.equals(set1) -> false list1.eq(set1) -> true list1.eq(set1) -> false
Tuple
Tuple
は一定数の要素を組み合わせて、ひとまとめにして扱うための型です。
■サンプルコード
import javaslang.Tuple; import javaslang.Tuple3; import static javaslang.API.*; // ID, 名前, 生年月日を組みにして保持するTuple Tuple3<Integer, String, LocalDate> tuple = Tuple.of(1, "Taro", LocalDate.of(1995, 12, 19)); println("src : " + tuple); Tuple3<Integer, String, LocalDate> dest = tuple.map( i -> i + 10, s -> "Yamada " + s, d -> d.plusYears(20) ); println("dest: " + dest); // 3番目の要素だけ変換したtupleを返す println(dest.map3(d -> d.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"))));
■実行結果
src : (1, Taro, 1995-12-19) dest: (11, Yamada Taro, 2015-12-19) (11, Yamada Taro, 2015/12/19)
Option
Option
は値があるかもしれないし無いかもしれないものを表現するための型です。Some
またはNone
いずれかのインスタンスになります。
Optionの生成
Option生成のためのファクトリメソッドを用いて何型の値が返されるのかを確認してみました
■サンプルコード
import javaslang.control.Option; import static javaslang.API.*; Option<String> opt1 = Option.of("a"); println("Option.of -> " + opt1.getClass()); Option<String> opt2 = Option.some("a"); println("Option.some -> " + opt2.getClass()); Option<String> opt3 = Option.of(null); println("Option.of(null) -> " + opt3.getClass()); Option<String> opt4 = Option.none(); println("Option.none -> " + opt4.getClass()); Option opt5 = Option.nothing(); println("Option.nothing -> " + opt5.getClass());
■実行結果
各メソッドで生成した結果どういう型で返ってきているかは以下の通りです。
Option#nothing
で生成するとSome
になるんですね。
Option.of -> class javaslang.control.Option$Some Option.some -> class javaslang.control.Option$Some Option.of(null) -> class javaslang.control.Option$None Option.none -> class javaslang.control.Option$None Option.nothing -> class javaslang.control.Option$Some
Option#nothing
Option#nothing
で生成したら値としては何が取得できるのか?
■サンプルコード
import javaslang.control.Option; Option opt = Option.nothing(); System.out.println(opt.getOrElse("unknown"));
■実行結果
null
nullが返ってきました。
Option#getOrElse と Option#getOrElseThrow
Option#getOrElse
, Option#getOrElseThrow
の動きの確認
■サンプルコード
import javaslang.control.Option; import static javaslang.API.*; Option<Integer> someOpt = Option.of(123); Option<Integer> emptyOpt = Option.of(null); int i1a = someOpt.getOrElse(-1); println("someOpt.getOrElse : " + i1a); int i1b = emptyOpt.getOrElse(-1); println("emptyOpt.getOrElse: " + i1b); int i2a = someOpt.getOrElse(() -> Integer.valueOf(100)); println("someOpt.getOrElse : " + i2a); int i2b = emptyOpt.getOrElse(() -> Integer.valueOf(100)); println("emptyOpt.getOrElse: " + i2b); try { int i3 = someOpt.getOrElseThrow(() -> new RuntimeException("someOpt not found")); println("someOpt.getOrElse : " + i3); } catch (RuntimeException e) { e.printStackTrace(); } try { int i3 = emptyOpt.getOrElseThrow(() -> new RuntimeException("emptyOpt not found")); println("emptyOpt.getOrElse: " + i3); } catch (RuntimeException e) { e.printStackTrace(); }
■実行結果
値が存在する(Someの)場合保持している値が取得できる、値が存在しない(Noneの)場合は各々それ用の処理(デフォルト値を返したり、例外をスローしたり)が実行されている
someOpt.getOrElse : 123 emptyOpt.getOrElse: -1 someOpt.getOrElse : 123 emptyOpt.getOrElse: 100 someOpt.getOrElse : 123 java.lang.RuntimeException: emptyOpt not found at trial.app.javaslang.Trial2.lambda$main$3(Trial2.java:33) at javaslang.control.Option.getOrElseThrow(Option.java:266) at trial.app.javaslang.Trial2.main(Trial2.java:33) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
java.util.Optionalを生成する
java.util.Optional
からOption
を生成することもできる
■サンプルコード
import javaslang.control.Option; import java.util.Optional; import static javaslang.API.*; // Option -> java.util.Optional Option<String> opt = Option.ofOptional(Optional.ofNullable("abcd")); println("Option value: " + opt.getOrElse("default1")); // java.util.Optional -> Option Optional<String> optional = opt.toJavaOptional(); println("Optional value: " + optional.orElse("default2"));
■実行結果
Option value: abcd Optional value: abcd
Either
Either
は、どちらか(Left or Right)を表現するための型です。一般的には正常値をRightに、エラーをLeftに割り当てるようです。
■サンプルコード
import javaslang.control.Either; public class EitherTrial { public static void main(String[] args) { logic("Abcdefgh").right() .map(s -> s.toUpperCase()) .forEach(System.out::println); logic("001").right() .map(s -> s.toUpperCase()) .forEach(System.out::println); } static Either<Throwable, String> logic(String s) { if (s.length() < 5) { return Either.left(new RuntimeException("invalid string.")); } return Either.right(s); } }
■実行結果
一回目の処理の呼び出しは正常値が戻ってきているので、そのまま後続の処理も行われていますが、
2回目の呼び出しでは内部的にエラーが返されているので、後続の処理は行われていません。
ABCDEFGH
Lazy
Lazy
は遅延評価される値を表現するための型です。
■サンプルコード
import javaslang.Lazy; import java.time.LocalDate; import static javaslang.API.*; public class LazyTrial { public static void main(String[] args) { Lazy<LocalDate> lazy = Lazy.of(LazyTrial::currentDate); println(">>1 -----------"); println(lazy.get()); println(">>2 -----------"); println(lazy.get()); println(">>3 -----------"); println(lazy.get()); } static LocalDate currentDate() { println("currentDate start."); LocalDate now = LocalDate.now(); println("currentDate end."); return now; } }
■実行結果
Lazy#get
が初めて呼ばれたタイミングで、中の処理が実行されて値を返します。
2回目以降の呼び出しではキャッシュされた値が返り、内部処理は実行されていません。
>>1 ----------- currentDate start. currentDate end. 2016-12-13 >>2 ----------- 2016-12-13 >>3 ----------- 2016-12-13
Future
Future
は非同期の計算処理を表現するための型です。Futureに対する操作はノンブロッキングで実行されます。
■サンプルコード
import javaslang.concurrent.Future; import java.time.LocalDateTime; import static javaslang.API.*; public class FutureTrial { public static void main(String[] args) { println(">> start"); Future<String> future1 = Future.of(FutureTrial::longProcessingTimeLogic) .onSuccess(s -> println("onSuccess -> longProcessingTimeLogic : " + s)) .onFailure(t -> println("onFailure -> longProcessingTimeLogic : " + t.getMessage())); Future<String> future2 = Future.of(FutureTrial::longProcessingTimeFailLogic) .onSuccess(s -> println("onSuccess -> longProcessingTimeFailLogic : " + s)) .onFailure(t -> println("onFailure -> longProcessingTimeFailLogic : " + t.getMessage())); println(">> executing..."); println("longProcessingTimeLogic result: [" + future1.getOrElse("empty") + "]"); println("longProcessingTimeFailLogic result: [" + future2.getOrElse("empty") + "]"); println(">> end"); } static String longProcessingTimeLogic() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } return "success: " + LocalDateTime.now().toString(); } static String longProcessingTimeFailLogic() { try { Thread.sleep(3500); } catch (InterruptedException e) { e.printStackTrace(); } throw new RuntimeException("error logic"); } }
■実行結果
>> start >> executing... onFailure -> longProcessingTimeFailLogic : error logic onSuccess -> longProcessingTimeLogic : success: 2016-12-15T19:50:30.447 longProcessingTimeLogic result: [success: 2016-12-15T19:50:30.447] longProcessingTimeFailLogic result: [empty] >> end
Promise
Promise
は成功あるいは失敗を表す値を一度だけ設定して、その値をFutureに渡すことができます。
Promise
とFuture
は以下のような役割分担になるようです。
Future
・・・ いつか誰かが値を渡してくれることを待っている型Promise
・・・ いつか値を設定する型
■サンプルコード
import javaslang.concurrent.Future; import javaslang.concurrent.Promise; import static javaslang.API.*; Promise<String> promise1 = Promise.make(); // いつか値が来るのを待っている Future<String> future1 = promise1.future() .onSuccess(s -> println("success: " + s)) .onFailure(t -> println("failed: " + t.getMessage())); // 成功を意味する値をセット promise1.success("1st try OK."); Promise<String> promise2 = Promise.make(); // いつか値が来るのを待っている Future<String> future2 = promise2.future() .onSuccess(s -> println("success: " + s)) .onFailure(t -> println("failed: " + t.getMessage())); // 失敗を意味する値をセット promise2.failure(new RuntimeException("2nd try NG."));
■実行結果
Promiseがセットし値を、Promiseによって生み出されたFutureが処理していることが分かります
success: 1st try OK. failed: 2nd try NG.
Try
Try
は正常な値を返すかもしれないし、エラーになるかもしれない処理を表現するための型です。
TryのインスタンスはSuccess
またはFailure
のいずれかのインスタンスになります。
■サンプルコード
import javaslang.control.Option; import javaslang.control.Try; import lombok.extern.slf4j.Slf4j; import static javaslang.API.*; @Slf4j public class TrySample { public static void main(String[] args) { int result1 = Try.of(() -> logic("123")) .getOrElse(-1); println("success result: " + result1); int result2 = Try.of(() -> logic(null)) .getOrElse(-1); println("failure result: " + result2); println("--------------------------");; String param1 = "normal-value"; Try.of(() -> logic(param1)) .onSuccess(i -> log.info("length: {}", i)) .onFailure(e -> log.error("error {}", e.getMessage())); String param2 = null; Try.of(() -> logic(param2)) .onSuccess(i -> log.info("length: {}", i)) .onFailure(e -> log.error("error {}", e.getMessage())); } // nullを引数に渡すと例外をスローするメソッド static int logic(String value) { return Option.of(value) .getOrElseThrow(() -> new NumberFormatException("value is null")) .length(); } }
■実行結果
success result: 3 failure result: -1 -------------------------- 2016/12/13 20:18:49.627 INFO [main] t.a.j.TrySample length: 12 2016/12/13 20:18:49.629 ERROR [main] t.a.j.TrySample error value is null
パターンマッチ
Javaslangでは、パターンマッチの機構をMatch
とCase
というstaticメソッドで提供しています。
整数値をパターンマッチで振り分ける
以下は整数値を受け取ってパターンマッチ用いて処理を振り分ける例です
■サンプルコード
import static javaslang.API.*; int i1 = 1; String s1 = Match(i1).of( Case($(1), "one"), Case($(2), "two"), Case($(), n -> n.toString()) ); println("s1: " + s1); int i2 = 0; String s2 = Match(i2).of( Case($(1), "one"), Case($(2), "two"), Case($(), n -> n.toString()) ); println("s2: " + s2);
■実行結果
s1: one s2: 0
Optionをパターンマッチで振り分ける
Option
をパターンマッチを用いて振り分ける例です
■サンプルコード
import javaslang.control.Option; import static javaslang.API.Match; import static javaslang.API.Case; import static javaslang.API.$; import static javaslang.API.println; import static javaslang.Patterns.*; Option<String> option1 = Option.of("test"); String result1 = Match(option1).of( Case(Some($()), s -> s.toUpperCase()), Case(None(), "empty") ); println("try1: " + result1); Option<String> option2 = Option.none(); String result2 = Match(option2).of( Case(Some($()), s -> s.toUpperCase()), Case(None(), "empty") ); println("try2: " + result2);
■実行結果
値がある場合は大文字化され、値が無ければ"empty"が返されています
try1: TEST try2: empty
Tryをパターンマッチで振り分ける
Try
の結果をパターンマッチを用いて振り分ける例です
■サンプルコード
import javaslang.control.Option; import javaslang.control.Try; import static javaslang.API.Match; import static javaslang.API.Case; import static javaslang.API.$; import static javaslang.API.println; import static javaslang.Patterns.*; public class MatchTrial { public static void main(String[] args) { Try<Integer> tryLogic1 = Try.of(() -> logic("data1")); Option<Integer> logicResult1 = Match(tryLogic1).of( Case(Success($()), v -> Option.of(v)), Case(Failure($()), e -> Option.none()) ); println("logicResult1: " + logicResult1.getOrElse(-1)); Try<Integer> tryLogic2 = Try.of(() -> logic(null)); Option<Integer> logicResult2 = Match(tryLogic2).of( Case(Success($()), v -> Option.of(v)), Case(Failure($()), e -> Option.none()) ); println("logicResult2: " + logicResult2.getOrElse(-1)); } // nullを渡すと例外をスローする static int logic(String value) { return Option.of(value) .getOrElseThrow(() -> new NumberFormatException("value is null")) .length(); } }
■実行結果
処理が失敗した場合は値が-1になっています
logicResult1: 5 logicResult2: -1
関数の合成(Composition)
複数の関数を合成して1つの関数を作ることができます。
サンプルでは引数String, 戻り値Stringの以下の関数を合成してみます。
- 空白を除去する関数
- 大文字化する関数
- 現在日を先頭に付与する関数
■サンプルコード
import javaslang.Function1; import java.time.LocalDate; import static javaslang.API.*; // 空白除去の関数 Function1<String, String> trimFunc = s -> s.trim(); // 大文字化の関数 Function1<String, String> upper = s -> s.toUpperCase(); // 先頭に現在日を付与する関数 Function1<String, String> headToCurrentDate = s -> LocalDate.now() + " : " + s; // andThenで合成 Function1<String, String> compFunc1 = trimFunc.andThen(upper).andThen(headToCurrentDate); println(compFunc1.apply("sample_string ")); // compose Function1<String, String> compFunc2 = trimFunc.compose(upper).compose(headToCurrentDate); println(compFunc2.apply("Hello, World! "));
■実行結果
2016-12-14 : SAMPLE_STRING 2016-12-14 : HELLO, WORLD!
Lifting
対象の関数の戻り値をOption
に入れて返す関数にするのがLiftingです。
引数の文字列がnullまたは空の場合にIllegalArgumentExceptionをスローする関数をLiftingしてみます
■サンプルコード
Function1#lift
で対象の処理(trimAndToUpper
)をLiftingしています。
import javaslang.Function1; import javaslang.control.Option; import static javaslang.API.*; public class LiftTrial { public static void main(String[] args) { Function1<String, Option<String>> liftedFunc = Function1.lift(LiftTrial::trimAndToUpper); // 正常なデータパターン String result1 = liftedFunc.apply("normal data ") .getOrElse("unknown"); println("result1: " + result1); // エラー扱いのデータパターン String result2 = liftedFunc.apply("") .getOrElse("unknown"); println("result2: " + result2); } // 引数がnullまたは空ならIllegalArgumentExceptionをスローする static String trimAndToUpper(String src) { if (src == null || src.isEmpty()) { throw new IllegalArgumentException("param is empty"); } return "[" + src.trim().toUpperCase() + "]"; } }
■実行結果
正常系の場合はSome
が返り、エラーの場合はNone
が返っていることが分かります
result1: [NORMAL DATA] result2: unknown
カリー化
引数のいくつかを固定して関数を部分適用するというのがカリー化です。
f = ax + y
という関数を定義して、このa
の部分に定数値をセットしてカリー化してみます。
■サンプルコード
import javaslang.Function1; import javaslang.Function3; import static javaslang.API.*; // y = a * x + y; Function3<Integer, Integer, Integer, Integer> sum3 = (a, x, y) -> a * x + y; // y = 10x + y (aの部分を10で固定してカリー化) Function1<Integer, Function1<Integer, Integer>> f1 = sum3.curried().apply(10); // y = 10 * 5 + 20 int result1 = f1.apply(5).apply(20); println("f1 result = " + result1); // y = 99x + y (aの部分を99で固定してカリー化) Function1<Integer, Function1<Integer, Integer>> f2 = sum3.curried().apply(99); // y = 99 * 5 + 20 int result2 = f2.apply(5).apply(20); println("f2 result = " + result2);
■実行結果
f1 result = 70 f2 result = 515
メモ化
メモ化された関数は一度だけ評価され、その後は同一のパラメータに対する関数呼び出しに関してはキャッシュから値が返されます。
1秒かけて足し算する関数をメモ化してみます。
■サンプルコード
slowFunction
が1秒かかって足し算を行うメソッドです。このメソッドをメモ化して呼び出しています。
実行時の処理経路が分かるようにログを各所で出力するようにしています。
import javaslang.Function2; import lombok.extern.slf4j.Slf4j; import static javaslang.API.*; @Slf4j public class MemoTrial { public static void main(String[] args) { Function2<Integer, Integer, Integer> sum = MemoTrial::slowFunction; // メモ化 Function2<Integer, Integer, Integer> memoizedSum = sum.memoized(); log.info("{} ", ">>1st 1 + 2 -----------"); // 1 + 2 int result1 = memoizedSum.apply(1).apply(2); log.info("1st: {} + {} = {}", 1, 2, result1); log.info("{} ", ">>2nd 1 + 2 -----------"); // 1 + 2 int result2 = memoizedSum.apply(1).apply(2); log.info("2nd: {} + {} = {}", 1, 2, result2); log.info("{} ", ">>1st 100 + 21 -----------"); // 100 + 21 int result3 = memoizedSum.apply(100).apply(21); log.info("1st: {} + {} = {}", 100, 21, result3); } static int slowFunction(int a, int b) { log.info("{} param: {} and {}", "logic start.", a, b); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } log.info("{} ", "logic end."); return a + b; } }
■実行結果
パラメータに1と2を指定した呼び出しは初回は遅いですが、2回目は即時に終わっていることが分かります。
新しいパラメータの100と21で呼び出すと、また時間がかかって結果が返っていてることが分かります。
2016/12/14 19:07:50.324 INFO [main] t.a.j.MemoTrial >>1st 1 + 2 ----------- 2016/12/14 19:07:50.331 INFO [main] t.a.j.MemoTrial logic start. param: 1 and 2 2016/12/14 19:07:51.331 INFO [main] t.a.j.MemoTrial logic end. 2016/12/14 19:07:51.331 INFO [main] t.a.j.MemoTrial 1st: 1 + 2 = 3 2016/12/14 19:07:51.331 INFO [main] t.a.j.MemoTrial >>2nd 1 + 2 ----------- 2016/12/14 19:07:51.331 INFO [main] t.a.j.MemoTrial 2nd: 1 + 2 = 3 2016/12/14 19:07:51.332 INFO [main] t.a.j.MemoTrial >>1st 100 + 21 ----------- 2016/12/14 19:07:51.332 INFO [main] t.a.j.MemoTrial logic start. param: 100 and 21 2016/12/14 19:07:52.332 INFO [main] t.a.j.MemoTrial logic end. 2016/12/14 19:07:52.332 INFO [main] t.a.j.MemoTrial 1st: 100 + 21 = 121
まとめ
- 色々理解できていない私にもJavaslang(Vavr)がかなり高機能な感じであることは分かりました。
- JavaslangのGitHub等にも書いてありますが、Java自体を拡張しようとしている感じがヒシヒシと伝わってきます。
- 今回試していないですが、
Seq
,Queue
,Vector
,Tree
,Array
,BitSet
,Validation
,Stream
...等も存在しています - 2017年にバージョン3.0が出るようです。かなり楽しみです。
私自身は、関数型プログラミングを理解するためにも以下の辺りの本読もうかと思います
![[増補改訂]関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus) [増補改訂]関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)](https://images-fe.ssl-images-amazon.com/images/I/51MNfQJfuUL._SL160_.jpg)
[増補改訂]関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)
- 作者: 大川徳之
- 出版社/メーカー: 技術評論社
- 発売日: 2016/09/24
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る

Scala関数型デザイン&プログラミング ―Scalazコントリビューターによる関数型徹底ガイド (impress top gear)
- 作者: Paul Chiusano,Rúnar Bjarnason,株式会社クイープ
- 出版社/メーカー: インプレス
- 発売日: 2015/03/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (7件) を見る