1. 初めに

Vavr (formerly called Javaslang) is a functional library for Java 8+ that provides persistent data types and functional control structures.

Vavr(正式名称は Javaslang)は、Java 8 以降の Java に、永続データ構造と関数型の制御構造を導入するライブラリです。

1.1. Java 8 と Vavr で関数型データ構造を扱う

Java 8’s lambdas (λ) empower us to create wonderful API’s. They incredibly increase the expressiveness of the language.

Java 8 で導入された ラムダ式(λ式)は素敵なAPIを作成するのに役立ちます。
ラムダ式は Java 言語の表現力を劇的に向上してくれました。

Vavr leveraged lambdas to create various new features based on functional patterns. One of them is a functional collection library that is intended to be a replacement for Java’s standard collections.

Vavr はラムダ式を活用して、関数型プログラミングのパターンに基づくさまざまな新しい機能を導入します。
中には Java の標準コレクションを置き換える関数型のコレクションライブラリも含みます。

Vavr Collections

(This is just a bird’s view, you will find a human-readable version below.)

(これは俯瞰的な図ですが、後で人間に読める大きさの文字にした図を紹介します。)

1.2. 関数型プログラミング

Before we deep-dive into the details about the data structures I want to talk about some basics. This will make it clear why I created Vavr and specifically new Java collections.

データ構造の詳細を説明する前に、いくつか基本知識を説明していきます。
そうすれば、なぜ Vavr を開発したのか、特に Java のコレクションを置き換えることにしたのか理解できるでしょう。

1.2.1. 副作用

Java applications are typically plentiful of side-effects. They mutate some sort of state, maybe the outer world. Common side effects are changing objects or variables in place, printing to the console, writing to a log file or to a database. Side-effects are considered harmful if they affect the semantics of our program in an undesirable way.

普通なら、Java アプリケーションにはさまざまな 副作用 があるものです。
内部状態だけでなく、外界を変化させる場合もあります。
一般的に副作用と言えば、その場のオブジェクトや変数を変化させることや、コンソールへ文字を出力すること、ログファイルやデータベースへデータを書き込むことが挙げられます。
プログラムの意図しない方法で、想定するセマンティクスに影響を与えることから、副作用は有害なものだと考えられています。

For example, if a function throws an exception and this exception is interpreted, it is considered as side-effect that affects our program. Furthermore exceptions are like non-local goto-statements. They break the normal control-flow. However, real-world applications do perform side-effects.

例えば、ある関数がスレッドへの割り込みにより例外を送出した場合、それはプログラムに影響を与える副作用だと考えられます。
また、 例外は大域的なGOTO文(みたいなもの)で、正常な制御フローを中断させてしまいます。
しかし、実世界のアプリケーションはそのような副作用を活用しているのです。

int divide(int dividend, int divisor) {
    // divisor が 0 のとき例外を送出します
    return dividend / divisor;
}

In a functional setting we are in the favorable situation to encapsulate the side-effect in a Try:

関数型のプログラミングスタイルは、副作用を Try にカプセル化することを推奨します。

// = Success(result) or Failure(exception)
Try<Integer> divide(Integer dividend, Integer divisor) {
    return Try.of(() -> dividend / divisor);
}

This version of divide does not throw any exception anymore. We made the possible failure explicit by using the type Try.

書き直した divide 関数は例外を送出しません。
潜在的な失敗を、Try 型で明示的に記述できるようになったのです。

1.2.2. 参照透過性

A function, or more generally an expression, is called referentially transparent if a call can be replaced by its value without affecting the behavior of the program. Simply spoken, given the same input the output is always the same.

プログラムの振る舞いに副作用を与えることなく、その呼び出しを値へ置き換えることができる関数、あるいは、より一般化した式のことを、 参照透過性がある、と表現します。
言い換えると、同じ入力を与えると常に同じ結果を返すということです。

// 参照透過性がありません
Math.random();

// 参照透過性があります
Math.max(1, 2);

A function is called pure if all expressions involved are referentially transparent. An application composed of pure functions will most probably just work if it compiles. We are able to reason about it. Unit tests are easy to write and debugging becomes a relict of the past.

参照透過性のある式および関数で構成された関数を 純粋関数と呼びます。
純粋関数だけで構成したアプリケーションは、コンパイルができればほとんどの場合ちゃんと動作します。
その理由は推測可能です。
ユニットテストを簡単に書けるようになり、デバッグ技法は過去の遺物になったことです。

1.2.3. 値について考える

Rich Hickey, the creator of Clojure, gave a great talk about The Value of Values. The most interesting values are immutable values. The main reason is that immutable values

  • are inherently thread-safe and hence do not need to be synchronized

  • are stable regarding equals and hashCode and thus are reliable hash keys

  • do not need to be cloned

  • behave type-safe when used in unchecked covariant casts (Java-specific)

Clojure言語の開発者であるRich Hickeyが、 値の価値について素晴らしいプレゼンテーションを公開しています。
最も興味深いのは 不変の値(オブジェクト)です。
不変の値は次のような性質を備えています。

  • 本質的にスレッド安全なので、同期が不要

  • equalshashCode も不変になるので、ハッシュ値(キー)が信頼できる

  • 複製を作らなくてよい

  • Java 特有の、共変型への強制型変換(キャスト)が未チェックでも、型安全に振る舞う

The key to a better Java is to use immutable values paired with referentially transparent functions.

より良い Java には不変の値参照透過な関数の組み合わせが必要不可欠です。

Vavr provides the necessary controls and collections to accomplish this goal in every-day Java programming.

Vavr は日々の Java プログラミングでより良い Java を記述するために必要な 制御構造コレクション を提供します。

1.3. データ構造入門

Vavr’s collection library comprises of a rich set of functional data structures built on top of lambdas. The only interface they share with Java’s original collections is Iterable. The main reason is that the mutator methods of Java’s collection interfaces do not return an object of the underlying collection type.

Vavr のコレクションライブラリは、ラムダ式を活用したさまざまな関数型のデータ構造を提供します。
Java の標準コレクションの中でも、Iterable インターフェイスだけは共有しています。
なぜなら、標準コレクションのインターフェイスに含まれる「状態や値を変更するメソッド(mutator method)」は、コレクションに格納しているデータ型のオブジェクトを返さないからです。

We will see why this is so essential by taking a look at the different types of data structures.

いろいろなデータ構造を見ながら、その理由について考えていきましょう。

1.3.1. 可変データ構造

Java is an object-oriented programming language. We encapsulate state in objects to achieve data hiding and provide mutator methods to control the state. The Java collections framework (JCF) is built upon this idea.

Java はオブジェクト指向プログラミング言語です。
データを隠蔽するために状態をオブジェクトにカプセル化し、状態を制御するために「状態や値を変更するメソッド(mutator method)」を提供します。
これが、 Java のコレクションフレームワークの元になっている考え方です。

interface Collection<E> {
    // コレクションから全ての要素を削除する
    void clear();
}

Today I comprehend a void return type as a smell. It is evidence that side-effects take place, state is mutated. Shared mutable state is an important source of failure, not only in a concurrent setting.

今の私は返り値の型が void であることをコードの悪臭として認識するようになりました。
副作用 の存在がその証拠です。
状態を変更してますよね。
可変の状態を 共有する のは、並行処理の有無に関わらず失敗の元でしかありません。

1.3.2. 不変データ構造

Immutable data structures cannot be modified after their creation. In the context of Java they are widely used in the form of collection wrappers.

不変データ構造は、一度作成したら二度と変更できません。
Java では、それぞれのコレクションに対応する不変コレクションがラッパーとして使われています。

List<String> list = Collections.unmodifiableList(otherList);

// バーン!
list.add("why not?");

There are various libraries that provide us with similar utility methods. The result is always an unmodifiable view of the specific collection. Typically it will throw at runtime when we call a mutator method.

さまざまなライブラリが似たようなユーティリティメソッドを提供しています。
どれも、元のコレクションに対応する変更不可能なビューを返すものです。
たいていの場合、「状態や値を変更するメソッド(mutator method)」を呼び出すと実行時に例外を送出するようになっています。

1.3.3. 永続データ構造

A persistent data structure does preserve the previous version of itself when being modified and is therefore effectively immutable. Fully persistent data structures allow both updates and queries on any version.

永続データ構造 は、変更する前の状態を記憶している、実質的な 不変性を備えているデータ構造です。
完全な永続データ構造なら、状態を変更できるだけでなく、任意の時点の状態を参照できます。

Many operations perform only small changes. Just copying the previous version wouldn’t be efficient. To save time and memory, it is crucial to identify similarities between two versions and share as much data as possible.

大半の操作が行うのは小規模な変更だけです。
ひとつ前の状態を複製するだけでは不十分です。
時間とメモリを節約するには、ひとつ前と今の状態の共通点を特定し、できるだけ共通点を共有する必要があります。

This model does not impose any implementation details. Here come functional data structures into play.

このモデルは具体的な実装を強制するものではありません。
次は関数型データ構造について説明します。

1.4. 関数型データ構造

Also known as purely functional data structures, these are immutable and persistent. The methods of functional data structures are referentially transparent.

純粋 関数型データ構造と呼ばれているデータ構造で、不変性永続性 を備えています。
関数型データ構造のメソッドは 参照透過 です。

Vavr features a wide range of the most-commonly used functional data structures. The following examples are explained in-depth.

Vavr は一般的に使用されているほとんどの関数型データ構造を提供しています。
ここでは、具体例で説明していきます。

1.4.1. 連結リスト

One of the most popular and also simplest functional data structures is the (singly) linked List. It has a head element and a tail List. A linked List behaves like a Stack which follows the last in, first out (LIFO) method.

(単一)連結リストは最も単純な関数型データ構造の1つです。
先頭(head) 要素と 末尾(tail) リストで構成されています。
連結リストは、 後入れ先出し(LIFO: last in, first out) のメソッドを持つスタックのように振る舞います。

In Vavr we instantiate a List like this:

Vavr では次のようにリストを生成できます。

// = List(1, 2, 3)
List<Integer> list1 = List.of(1, 2, 3);

Each of the List elements forms a separate List node. The tail of the last element is Nil, the empty List.

リストのそれぞれの要素は独立したリストのノードです。
最後の要素の tail は Nil すなわち空リストです。

List 1

This enables us to share elements across different versions of the List.

Vavr では同じ要素を複数のリストで共有できます。

// = List(0, 2, 3)
List<Integer> list2 = list1.tail().prepend(0);

The new head element 0 is linked to the tail of the original List. The original List remains unmodified.

新しい head 要素 0 は元のリストの tail に 連結 しています。
元のリストはそのまま残っているのです。

List 2

These operations take place in constant time, in other words they are independent of the List size. Most of the other operations take linear time. In Vavr this is expressed by the interface LinearSeq, which we may already know from Scala.

それぞれの操作には、リストの大きさとは関係なく一定の時間(定数時間)がかかります。
他のほとんどの操作には線形時間がかかります。
Vavr ではそのような操作を LinearSeq インターフェイスとして表現しています。
ご存じの通り Scala からの借用です。

If we need data structures that are queryable in constant time, Vavr offers Array and Vector. Both have random access capabilities.

Vavr において、定数時間でアクセスできるデータ構造が必要なときは、Array か Vector を使います。
どちらも ランダムアクセス 機能を備えているからです。

The Array type is backed by a Java array of objects. Insert and remove operations take linear time. Vector is in-between Array and List. It performs well in both areas, random access and modification.

Array 型の実装は Java の配列です。
追加と削除には線形時間がかかります。
Vector は Array と List の中間です。
ランダムアクセスと変更操作のどちらについても、そこそこの性能を示します。

In fact the linked List can also be used to implement a Queue data structure.

そういえば、連結リストを使うとキューを実装できます。

1.4.2. キュー

A very efficient functional Queue can be implemented based on two linked Lists. The front List holds the elements that are dequeued, the rear List holds the elements that are enqueued. Both operations enqueue and dequeue perform in O(1).

2つの連結リストを使うと、非常に効率のよい関数型のキューを実装できます。
前方(front) リストは キューから取り出された(dequeue) 要素を保持します。
後方(rear) リストは キューに追加された(enqueue) 要素を保持します。
enqueue と dequeue のどちらの操作も実行時間は O(1) です。

Queue<Integer> queue = Queue.of(1, 2, 3)
                            .enqueue(4)
                            .enqueue(5);

The initial Queue is created of three elements. Two elements are enqueued on the rear List.

このコードでは、3要素のキューを作成し、rear に2つの要素を enqueue しています。

Queue 1

If the front List runs out of elements when dequeueing, the rear List is reversed and becomes the new front List.

dequeue するときに front が一杯になってしまったら、rear を逆順にして新たに front として使用します。

Queue 2

When dequeueing an element we get a pair of the first element and the remaining Queue. It is necessary to return the new version of the Queue because functional data structures are immutable and persistent. The original Queue is not affected.

キューから dequeue すると、先頭要素と後続キューの対が得られます。
関数型データ構造は不変性と永続性を備えているので、新たなキューを返さなければならないからです。
元のキューには影響しません。

Queue<Integer> queue = Queue.of(1, 2, 3);

// = (1, Queue(2, 3))
Tuple2<Integer, Queue<Integer>> dequeued =
        queue.dequeue();

What happens when the Queue is empty? Then dequeue() will throw a NoSuchElementException. To do it the functional way we would rather expect an optional result.

空のキューに操作するとどうなるでしょうか。
dequeue は NoSucheElementException をスローするでしょう。
関数型プログラミングのやり方 ではオプショナルを返すことが期待されます。

// = Some((1, Queue()))
Queue.of(1).dequeueOption();

// = None
Queue.empty().dequeueOption();

An optional result may be further processed, regardless if it is empty or not.

返り値のオプショナルは空であろうとなかろうと、そのまま処理を継続できます。

// = Queue(1)
Queue<Integer> queue = Queue.of(1);

// = Some((1, Queue()))
Option<Tuple2<Integer, Queue<Integer>>> dequeued =
        queue.dequeueOption();

// = Some(1)
Option<Integer> element = dequeued.map(Tuple2::_1);

// = Some(Queue())
Option<Queue<Integer>> remaining =
        dequeued.map(Tuple2::_2);

1.4.3. 順序付きセット

Sorted Sets are data structures that are more frequently used than Queues. We use binary search trees to model them in a functional way. These trees consist of nodes with up to two children and values at each node.

順序付きセットはキューよりよく使われるデータ構造で、関数型データ構造としては2分探索木で実装できます。
2分探索木は最大2つの子ノードと1つの値を持つノードで構成されています。

We build binary search trees in the presence of an ordering, represented by an element Comparator. All values of the left subtree of any given node are strictly less than the value of the given node. All values of the right subtree are strictly greater.

Vavr ではそれぞれの要素に対応する Comparator に基づく順序付けにより、2分探索木を構築しています。
あらゆるノードの左部分木の値はそのノードの値より必ず小さくなりますし、右部分木の値はそのノードの値より必ず大きくなります。

// = TreeSet(1, 2, 3, 4, 6, 7, 8)
SortedSet<Integer> xs = TreeSet.of(6, 1, 3, 2, 4, 7, 8);
Binary Tree 1

Searches on such trees run in O(log n) time. We start the search at the root and decide if we found the element. Because of the total ordering of the values we know where to search next, in the left or in the right branch of the current tree.

木の探索にかかる時間は O(log n) です。
探索は根から開始して、目的の要素が見つかるまで続けます。
全ての値は順序付けられているので、今のノードから左と右のどちらの部分木を探索するか判断できるのです。

// = TreeSet(1, 2, 3);
SortedSet<Integer> set = TreeSet.of(2, 3, 1, 2);

// = TreeSet(3, 2, 1);
Comparator<Integer> c = (a, b) -> b - a;
SortedSet<Integer> reversed = TreeSet.of(c, 2, 3, 1, 2);

Most tree operations are inherently recursive. The insert function behaves similarly to the search function. When the end of a search path is reached, a new node is created and the whole path is reconstructed up to the root. Existing child nodes are referenced whenever possible. Hence the insert operation takes O(log n) time and space.

木に関するほとんどの操作は本質的に 再帰的になります。
挿入は探索と同じように行います。
探索位置が終端に到達したら新しいノードを作成し、根に向かって木を再構築します。
できるだけ既存のノードを再利用するようにします。
挿入は時間と空間のどちらにも O(log n) のコストがかかります。

// = TreeSet(1, 2, 3, 4, 5, 6, 7, 8)
SortedSet<Integer> ys = xs.add(5);
Binary Tree 2

In order to maintain the performance characteristics of a binary search tree it needs to be kept balanced. All paths from the root to a leaf need to have roughly the same length.

2分木の性能特性を保証するには、定期的な木の再編成が必要です。
根から全ての葉に対する距離が、ほぼ等しくなるようにしなければなりません。

In Vavr we implemented a binary search tree based on a Red/Black Tree. It uses a specific coloring strategy to keep the tree balanced on inserts and deletes. To read more about this topic please refer to the book Purely Functional Data Structures by Chris Okasaki.

Vavr では2分木を 赤黒木に基づいて実装しています。
木へ挿入したり、削除したりするとき、特定の色戦略で木が均等になるようにします。
詳細なアルゴリズムに興味があるなら、Chris Okasaki の著書 Purely Functional Data Structures を参照してください。

1.5. 最先端のコレクション

Generally we are observing a convergence of programming languages. Good features make it, other disappear. But Java is different, it is bound forever to be backward compatible. That is a strength but also slows down evolution.

普通のプログラミング言語は、良いところを残してそれ以外を捨てていくものだと考えています。
しかし Java はそうしませんでした。
後方互換性を保証することにしたのです。
プログラミング言語としての信頼性は高まりましたが、進化の速度を遅くさせてしまう決定でした。

Lambda brought Java and Scala closer together, yet they are still so different. Martin Odersky, the creator of Scala, recently mentioned in his BDSBTB 2015 keynote the state of the Java 8 collections.

ラムダ式により Java は Scala と遜色ない言語になりましたが、それでも大きく異なる部分は残っています。
Scala 言語の作者である Martin Odersky は、 BDSBTB 2015 keynote で Java 8 の標準コレクションの先進性について言及しています。

He described Java’s Stream as a fancy form of an Iterator. The Java 8 Stream API is an example of a lifted collection. What it does is to define a computation and link it to a specific collection in another explicit step.

Martin Odersky は Java の Stream をとても手の込んだ Iterator だと述べています。
Java 8 で導入された Stream API は、コレクションを 新しい水準へ引き上げる 恰好の例だというのです。
すなわち、計算という概念を 定義し 、任意のコレクションを何らかの処理手続きと 結びつける のです。

// i + 1
i.prepareForAddition()
 .add(1)
 .mapBackToInteger(Mappers.toInteger())

This is how the new Java 8 Stream API works. It is a computational layer above the well known Java collections.

Java 8 の Stream API は、既存のコレクションへ計算レイヤーを追加するものだったのです。

// = ["1", "2", "3"] in Java 8
Arrays.asList(1, 2, 3)
      .stream()
      .map(Object::toString)
      .collect(Collectors.toList())

Vavr is greatly inspired by Scala. This is how the above example should have been in Java 8.

Vavr の設計は Scala 言語から多くの発想を得ています。
ですので、Vavr では前のコード例を次のように記述できるようにします。

// = Stream("1", "2", "3") in Vavr
Stream.of(1, 2, 3).map(Object::toString)

Within the last year we put much effort into implementing the Vavr collection library. It comprises the most widely used collection types.

昨年はかなりの時間を Vavr のコレクションライブラリの実装に費やしました。
主に、最も広く使われているコレクション型 Seq が対象です。

1.5.1. 並び

We started our journey by implementing sequential types. We already described the linked List above. Stream, a lazy linked List, followed. It allows us to process possibly infinite long sequences of elements.

最初はシーケンシャル型を実装することにしました。
連結リスト、Stream、遅延連結リストが出来ているので、無限シーケンスを作成できます。

Seq

All collections are Iterable and hence could be used in enhanced for-statements.

Vavr の全てのコレクションは Iterable を実装しているので、拡張 for 文で使用できます。

for (String s : List.of("Java", "Advent")) {
    // side effects and mutation
}

We could accomplish the same by internalizing the loop and injecting the behavior using a lambda.

内部イテレータとラムダ式で同じ処理を記述できます。

List.of("Java", "Advent").forEach(s -> {
    // side effects and mutation
});

Anyway, as we previously saw we prefer expressions that return a value over statements that return nothing. By looking at a simple example, soon we will recognize that statements add noise and divide what belongs together.

いずれにしても、私たちは値を返す式のほうが適切であるという考えを説明してきました。
次のようなコード例を見れば、文が邪魔して理解を妨げているのが分かるでしょう。

String join(String... words) {
    StringBuilder builder = new StringBuilder();
    for(String s : words) {
        if (builder.length() > 0) {
            builder.append(", ");
        }
        builder.append(s);
    }
    return builder.toString();
}

The Vavr collections provide us with many functions to operate on the underlying elements. This allows us to express things in a very concise way.

Vavr のコレクションは、それぞれの要素に何らかの処理を適用するためのさまざまな関数を提供しています。
つまり、次のように簡潔に記述できるということです。

String join(String... words) {
    return List.of(words)
               .intersperse(", ")
               .foldLeft(new StringBuilder(), StringBuilder::append)
               .toString();
}

Most goals can be accomplished in various ways using Vavr. Here we reduced the whole method body to fluent function calls on a List instance. We could even remove the whole method and directly use our List to obtain the computation result.

Vavr はたいていの問題を記述するための方法を提供します。
メソッド全体を List オブジェクトに対する fluent な関数の呼び出しで実装できます。
メソッド自体を無くして List から直接敵に計算結果を取得することもできるのです。

List.of(words).mkString(", ");

In a real world application we are now able to drastically reduce the number of lines of code and hence lower the risk of bugs.

実際のアプリケーション実装に必要とされる何行ものコードを節約できるので、バグの混入リスクも低下します。

1.5.2. セット(Set)とマップ(Map)

Sequences are great. But to be complete, a collection library also needs different types of Sets and Maps.

シーケンスは有能ですが何でも解決できるわけではありません。
コレクションライブラリにはセット(Set)とマップ(Map)が必要です。

Set and Map

We described how to model sorted Sets with binary tree structures. A sorted Map is nothing else than a sorted Set containing key-value pairs and having an ordering for the keys.

ここまでに、二分木に基づく順序付きセットについて説明しました。
順序付きマップとは、キーと値の対を要素とし、キーの順番に整列された順序付きセットそのものです。

The HashMap implementation is backed by a Hash Array Mapped Trie (HAMT). Accordingly the HashSet is backed by a HAMT containing key-key pairs.

HashMap は Hash Array Mapped Trie (HAMT) に基づいて実装されています。
同様に、HashSet もキーと値の対を要素とする HAMT で実装されています。

Our Map does not have a special Entry type to represent key-value pairs. Instead we use Tuple2 which is already part of Vavr. The fields of a Tuple are enumerated.

Vavr の提供するマップには、キーと値の対を表現する特別な型が ありません
Tuple2 という型を代用します。
Tuple のフィールドは列挙可能です。

// = (1, "A")
Tuple2<Integer, String> entry = Tuple.of(1, "A");

Integer key = entry._1;
String value = entry._2;

Maps and Tuples are used throughout Vavr. Tuples are inevitable to handle multi-valued return types in a general way.

Vavr ではあらゆる場面でマップとタプル(Tuple)を使用します。
多値を返り値にするにはタプルを使うのが一般的な方法です。

// = HashMap((0, List(2, 4)), (1, List(1, 3)))
List.of(1, 2, 3, 4).groupBy(i -> i % 2);

// = List((a, 0), (b, 1), (c, 2))
List.of('a', 'b', 'c').zipWithIndex();

At Vavr, we explore and test our library by implementing the 99 Euler Problems. It is a great proof of concept. Please don’t hesitate to send pull requests.

Vavr ではライブラリの検査やテストのために 99 Euler Problems を実装しています。
概念実証として比肩するものはないでしょう。
恐れることなく、変更の提案(プルリクエスト)を作成してください。

2. Vavr 入門

Projects that include Vavr need to target Java 1.8 at minimum.

Vavr を使用するプロジェクトのコンパイルバージョンは、最低でも Java 1.8 にしなければなりません。

The .jar is available at Maven Central.

Vavr の jar ファイルは Maven Central で公開されています。

2.1. Gradle

dependencies {
    compile "io.vavr:vavr:0.10.3"
}

2.2. Maven

<dependencies>
    <dependency>
        <groupId>io.vavr</groupId>
        <artifactId>vavr</artifactId>
        <version>0.10.3</version>
    </dependency>
</dependencies>

2.3. スタンドアローン

Because Vavr does not depend on any libraries (other than the JVM) you can easily add it as standalone .jar to your classpath.

Vavr の依存ライブラリは Java の標準ライブラリだけなので、jar ファイルをクラスパスに追加するだけで使い始められるようになっています。

2.4. スナップショット

Developer versions can be found here.

開発中のバージョンは SonartypeのMavenリポジトリ で公開されています。

2.4.1. Gradle

Add the additional snapshot repository to your build.gradle:

プロジェクトの build.gradle へスナップショットリポジトリを追加してください。

repositories {
    (...)
    maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
}

2.4.2. Maven

Ensure that your ~/.m2/settings.xml contains the following:

~/.m2/settings.xml へスナップショットリポジトリを追加してください。

<profiles>
    <profile>
        <id>allow-snapshots</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <repositories>
            <repository>
                <id>snapshots-repo</id>
                <url>https://oss.sonatype.org/content/repositories/snapshots</url>
                <releases>
                    <enabled>false</enabled>
                </releases>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </repository>
        </repositories>
    </profile>
</profiles>

3. 利用ガイド

Vavr comes along with well-designed representations of some of the most basic types which apparently are missing or rudimentary in Java: Tuple, Value and λ.
In Vavr, everything is built upon these three basic building blocks:

Vavr は、これまでの Java に不足していた基本的な型の Tuple および Value そして λ に関する適切な表現に基づいています。
あらゆる要素がこれらの基本的な構成要素で成り立っているといっても過言ではありません。

Vavr Overview

3.1. タプル

Java is missing a general notion of tuples. A Tuple combines a fixed number of elements together so that they can be passed around as a whole. Unlike an array or list, a tuple can hold objects with different types, but they are also immutable.
Tuples are of type Tuple1, Tuple2, Tuple3 and so on. There currently is an upper limit of 8 elements. To access elements of a tuple t, you can use method t._1 to access the first element, t._2 to access the second, and so on.

Java にはタプルを記述する方法がありません。
タプルとは、有限数の要素を束ねて1つの要素として扱えるようにするものです。
配列やリストと違って異なる型のオブジェクトを持つことができますが、それぞれの要素は不変でなければなりません。
Vavr は Tuple1Tuple2Tuple3 など要素数に対応する型を提供します。
今のところ Tuple8 が最大です。
タプル t について、t._1 で先頭の要素へ、t._2 で2番目の要素へアクセスできます。

3.1.1. タプルの作成

Here is an example of how to create a tuple holding a String and an Integer:

String と Integer を持つタプルは次のように作成します。

// (Java, 8)
Tuple2<String, Integer> java8 = Tuple.of("Java", 8); (1)

// "Java"
String s = java8._1; (2)

// 8
Integer i = java8._2; (3)
1 静的ファクトリメソッド Tuple.of() でタプルを作成します
2 先頭要素にアクセスします
3 2番目の要素にアクセスします

3.1.2. タプルの要素ごとにマップする

The component-wise map evaluates a function per element in the tuple, returning another tuple.

タプルの要素ごとに関数を評価して、新しいタプルを返すことができます。

// (vavr, 1)
Tuple2<String, Integer> that = java8.map(
        s -> s.substring(2) + "vr",
        i -> i / 8
);

3.1.3. タプル全体をマップする

It is also possible to map a tuple using one mapping function.

タプル全体を対象に関数を評価できます。

// (vavr, 1)
Tuple2<String, Integer> that = java8.map(
        (s, i) -> Tuple.of(s.substring(2) + "vr", i / 8)
);

3.1.4. タプルを変換する

Transform creates a new type based on the tuple’s contents.

Transform はタプルの内容に基づく新しいオブジェクトを生成します。

// "vavr 1"
String that = java8.apply(
        (s, i) -> s.substring(2) + "vr " + i / 8
);

3.2. 関数

Functional programming is all about values and transformation of values using functions. Java 8 just provides a Function which accepts one parameter and a BiFunction which accepts two parameters. Vavr provides functions up to a limit of 8 parameters. The functional interfaces are of called Function0, Function1, Function2, Function3 and so on. If you need a function which throws a checked exception you can use CheckedFunction1, CheckedFunction2 and so on.
The following lambda expression creates a function to sum two integers:

関数型プログラミングでは、値と、関数による値の変換で全てを表現します。
Java 8 では、1つの引数を受け取る Function や、2つの引数を受け取る BiFunction が導入されました。
Vavr では最大8つの引数に対応した関数型インターフェイスの Function0Function1 (以下省略)を提供します。
関数がチェック例外を送出しなければならないときのために CheckedFunction1CheckedFunction2 (以下省略)を提供します。
次のコード例は、2つの整数を加算する関数を生成するラムダ式です。

// sum.apply(1, 2) = 3
Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;

This is a shorthand for the following anonymous class definition:

これは、次のような匿名クラスの定義の短縮形にすぎません。

Function2<Integer, Integer, Integer> sum = new Function2<Integer, Integer, Integer>() {
    @Override
    public Integer apply(Integer a, Integer b) {
        return a + b;
    }
};

You can also use the static factory method Function3.of(…​) to a create a function from any method reference.

静的ファクトリメソッドの Function3.of(…​) を使うと、任意のメソッド参照から関数を作成できます。

Function3<String, String, String, String> function3 =
        Function3.of(this::methodWhichAccepts3Parameters);

In fact Vavr functional interfaces are Java 8 functional interfaces on steroids. They also provide features like:

  • Composition

  • Lifting

  • Currying

  • Memoization

Vavr の関数型インターフェイスは Java 8 の関数型インターフェイスを強化します。
具体的には次のような機能を拡張します。

  • 合成

  • 引き上げ

  • カリー化

  • メモ化

3.2.1. 合成

You can compose functions. In mathematics, function composition is the application of one function to the result of another to produce a third function. For instance, the functions f→: X → Y and g→: Y → Z can be composed to yield a function h: g(f(x)) which maps X → Z.
You can use either andThen:

関数は合成できます。
数学的には、ある関数の結果を他の関数に適用する関数を生成するのが関数合成です。
例えば、関数 f→:X→Y と関数 g→:Y→Z を合成した関数は h: g(f(x)) となり、この関数は X' を `Z へ写像することになります。
andThen メソッドで同じことができます。

Function1<Integer, Integer> plusOne = a -> a + 1;
Function1<Integer, Integer> multiplyByTwo = a -> a * 2;

Function1<Integer, Integer> add1AndMultiplyBy2 = plusOne.andThen(multiplyByTwo);

then(add1AndMultiplyBy2.apply(2)).isEqualTo(6);

or compose:

あるいは compose でも同様です。

Function1<Integer, Integer> add1AndMultiplyBy2 = multiplyByTwo.compose(plusOne);

then(add1AndMultiplyBy2.apply(2)).isEqualTo(6);

3.2.2. 引き上げ

You can lift a partial function into a total function that returns an Option result. The term partial function comes from mathematics. A partial function from X to Y is a function f: X′ → Y, for some subset X′ of X. It generalizes the concept of a function f: X → Y by not forcing f to map every element of X to an element of Y. That means a partial function works properly only for some input values. If the function is called with a disallowed input value, it will typically throw an exception.

不完全関数を、Option を返す完全関数へ引き上げることができます。
不完全関数 という単語は数学からの借用です。
X を Y に写像する不完全関数とは、X の部分集合 X` を写像する関数 f:X`→Y のことです。
これは、関数 f:X→Y が、Xの全ての要素をYに写像できない場合も含むよう一般化する考え方です。
つまり、不完全関数は一部の入力に対してのみ正常に機能することになります。
たいていの場合、不適切な値で呼び出された不完全関数は例外を送出します。

The following method divide is a partial function that only accepts non-zero divisors.

次のコード例の divide メソッドは、0以外の値だけを許容する不完全関数です。

Function2<Integer, Integer, Integer> divide = (a, b) -> a / b;

We use lift to turn divide into a total function that is defined for all inputs.

divide メソッドを lift すると、全ての入力を許容する完全関数へ引き上げられます。

Function2<Integer, Integer, Option<Integer>> safeDivide = Function2.lift(divide);

// = None
Option<Integer> i1 = safeDivide.apply(1, 0); (1)

// = Some(2)
Option<Integer> i2 = safeDivide.apply(4, 2); (2)
1 引き上げた関数は、許容しない値を渡すと例外を送出する代わりに None を返します。
2 引き上げた関数は、許容する値を渡すと Some を返します。

The following method sum is a partial function that only accepts positive input values.

次のコード例の sum メソッドは、正の値だけを許容する不完全関数です。

int sum(int first, int second) {
    if (first < 0 || second < 0) {
        throw new IllegalArgumentException("Only positive integers are allowed"); (1)
    }
    return first + second;
}
1 負の値を渡すと sumIllegalArgumentException を送出します。

We may lift the sum method by providing the methods reference.

sum メソッドを lift するには、メソッド参照を渡すこともできます。

Function2<Integer, Integer, Option<Integer>> sum = Function2.lift(this::sum);

// = None
Option<Integer> optionalResult = sum.apply(-1, 2); (1)
1 引き上げた関数は IllegalArgumentException を検出して None へ変換します。

3.2.3. 部分適用

Partial application allows you to derive a new function from an existing one by fixing some values. You can fix one or more parameters, and the number of fixed parameters defines the arity of the new function such that new arity = (original arity - fixed parameters). The parameters are bound from left to right.

関数の引数の一部を固定した新しい関数を生成するのが部分適用です。
固定する引数は1つでも2つでも構いません(任意です)。
固定した引数の数が、新しく生成した関数のアリティになります(新しい関数のアリティ = 元の関数のアリティ - 固定した引数の数)。
引数は左から右に向かって固定できます。

Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;
Function1<Integer, Integer> add2 = sum.apply(2); (1)

then(add2.apply(4)).isEqualTo(6);
1 最初の引数 a を値 2 で固定します。

This can be demonstrated by fixing the first three parameters of a Function5, resulting in a Function2.

具体的には、Function5 の最初の3つの引数を固定すると Function2 になります。

Function5<Integer, Integer, Integer, Integer, Integer, Integer> sum = (a, b, c, d, e) -> a + b + c + d + e;
Function2<Integer, Integer, Integer> add6 = sum.apply(2, 3, 1); (1)

then(add6.apply(4, 3)).isEqualTo(13);
1 引数 a b c を、それぞれ値 2 3 1 で固定します。

Partial application differs from Currying, as will be explored in the relevant section.

部分適用とCurryingは別の概念です。カリー化について詳しくは後で説明します。

3.2.4. カリー化

Currying is a technique to partially apply a function by fixing a value for one of the parameters, resulting in a Function1 function that returns a Function1.

カリー化とは、関数にいずれかの引数を部分適用するテクニックのことで、Function1 をカリー化すると Function1 を返すことになります。

When a Function2 is curried, the result is indistinguishable from the partial application of a Function2 because both result in a 1-arity function.

Function2カリー化 した結果は、部分適用 の考え方では区別できません。
なぜなら、Function2 には引数が2つあるため、どちらの引数を固定しても、アリティが1の関数になるからです。

Function2<Integer, Integer, Integer> sum = (a, b) -> a + b;
Function1<Integer, Integer> add2 = sum.curried().apply(2); (1)

then(add2.apply(4)).isEqualTo(6);
1 最初の引数 a を値 2 で固定します。

You might notice that, apart from the use of .curried(), this code is identical to the 2-arity given example in Partial application. With higher-arity functions, the difference becomes clear.

.curried() を使っていること以外は Partial application のコード例とまったく変わらないことに気付いたでしょうか。
アリティの高い関数なら違いが明確になります。

Function3<Integer, Integer, Integer, Integer> sum = (a, b, c) -> a + b + c;
final Function1<Integer, Function1<Integer, Integer>> add2 = sum.curried().apply(2);(1)

then(add2.apply(4).apply(3)).isEqualTo(9); (2)
1 新しい関数にはまだ2つ引数が必要です。
2 1つ目の apply は新たな Function1 を生成します。2つ目の apply は結果を返します。

3.2.5. メモ化

Memoization is a form of caching. A memoized function executes only once and then returns the result from a cache.
The following example calculates a random number on the first invocation and returns the cached number on the second invocation.

メモ化とはキャッシュの一種です。
メモ化した関数は最初の呼び出しだけ実際に計算し、それ以降の呼び出しではキャッシュした結果を返します。
次のコード例は、最初の呼び出しでランダムな数値を計算して、それ以降の呼び出しではキャッシュした結果を返しています。

Function0<Double> hashCache =
        Function0.of(Math::random).memoized();

double randomValue1 = hashCache.apply();
double randomValue2 = hashCache.apply();

then(randomValue1).isEqualTo(randomValue2);

3.3. 値

In a functional setting we see a value as a kind of normal form, an expression which cannot be further evaluated. In Java we express this by making the state of an object final and call it immutable.
Vavr’s functional Value abstracts over immutable objects. Efficient write operations are added by sharing immutable memory between instances. What we get is thread-safety for free!
関数型プログラミングでは、値を 正規形 として扱います。
つまり、1度しか評価できない式と同じ扱いなのです。
Java では final にしたオブジェクトに相当し、これを 不変オブジェクト と呼びます。
Vavr の抽象としての値は、不変オブジェクトより多くの機能を提供します。
インスタンス間で不変オブジェクトを共有する、より効率的な書き込み操作ができるようになっているのです。
これは、スレッド安全性を獲得するのに役立ちました。

3.3.1. Option

Option is a monadic container type which represents an optional value. Instances of Option are either an instance of Some or the None.

Optionはモナドのコンテナ型で、任意の値の存在を表現します。
Optionのインスタンスは、Some のインスタンスか None のいずれかになります。

// optional *value*, no more nulls
Option<T> option = Option.of(...);

If you’re coming to Vavr after using Java’s Optional class, there is a crucial difference. In Optional, a call to .map that results in a null will result in an empty Optional. In Vavr, it would result in a Some(null) that can then lead to a NullPointerException.

Java の Optional を知ってから Vavr を使い始めたとしたら、決定的な違いがあることに気付いているでしょう。
空の Optional について .map で値を取り出すと、null が返ってくるのです。
Vavr では Some(null) から値を取り出そうとすると NullPointerException を送出します。

Using Optional, this scenario is valid.

次のコード例は Optional なら正しい使い方です。

Optional<String> maybeFoo = Optional.of("foo"); (1)
then(maybeFoo.get()).isEqualTo("foo");
Optional<String> maybeFooBar = maybeFoo.map(s -> (String)null)  (2)
                                       .map(s -> s.toUpperCase() + "bar");
then(maybeFooBar.isPresent()).isFalse();
1 Optionでは Some("foo") と同じ意味になります。
2 この行のせいで、最終的なOptionは空になります。

Using Vavr’s Option, the same scenario will result in a NullPointerException.

Vavr の Option で同じことをすると NullPointerException を送出します。

Option<String> maybeFoo = Option.of("foo"); (1)
then(maybeFoo.get()).isEqualTo("foo");
try {
    maybeFoo.map(s -> (String)null) (2)
            .map(s -> s.toUpperCase() + "bar"); (3)
    Assert.fail();
} catch (NullPointerException e) {
    // this is clearly not the correct approach
}
1 Optionは Some("foo") になります。
2 Optionは Some(null) になります。
3 null に対して s.toUpperCase() を呼び出したことになります。

This seems like Vavr’s implementation is broken, but in fact it’s not - rather, it adheres to the requirement of a monad to maintain computational context when calling .map. In terms of an Option, this means that calling .map on a Some will result in a Some, and calling .map on a None will result in a None. In the Java Optional example above, that context changed from a Some to a None.

Vavr の実装が間違っていると思うかもしれませんが、そんなことはありません。
それどころか、モナドの要求する .map を呼ぶときの計算の文脈を守っているのです。
Option の定義では、Some について .map を呼び出した結果は Some になりますし、None について .map を呼び出した結果は None になるからです。
Java の Optional では、Some から None へ文脈が変化してしまっているのです。

This may seem to make Option useless, but it actually forces you to pay attention to possible occurrences of null and deal with them accordingly instead of unknowingly accepting them. The correct way to deal with occurrences of null is to use flatMap.

Option が使いにくいと感じるかもしれませんが、この制限は null の発生する可能性について注意を促すことになるし、未知の値を無視せず、適切に扱えるようにしてくれます。
null の発生する場合を考慮する正しい方法は flatMap を使う方法です。

Option<String> maybeFoo = Option.of("foo"); (1)
then(maybeFoo.get()).isEqualTo("foo");
Option<String> maybeFooBar = maybeFoo.map(s -> (String)null) (2)
                                     .flatMap(s -> Option.of(s) (3)
                                                         .map(t -> t.toUpperCase() + "bar"));
then(maybeFooBar.isEmpty()).isTrue();
1 Optionは Some("foo") になります。
2 Optionは Some(null) になります。
3 snull なので None になります。

Alternatively, move the .flatMap to be co-located with the the possibly null value.

また、null の発生しそうな部分に .flatMap を移動してもいいでしょう。

Option<String> maybeFoo = Option.of("foo"); (1)
then(maybeFoo.get()).isEqualTo("foo");
Option<String> maybeFooBar = maybeFoo.flatMap(s -> Option.of((String)null)) (2)
                                     .map(s -> s.toUpperCase() + "bar");
then(maybeFooBar.isEmpty()).isTrue();
1 Optionは Some("foo") になります。
2 Optionは None になります。

This is explored in more detail on the Vavr blog.

この内容について、 Vavr のブログ でより詳しく説明しています。

3.3.2. Try

Try is a monadic container type which represents a computation that may either result in an exception, or return a successfully computed value. It’s similar to, but semantically different from Either. Instances of Try, are either an instance of Success or Failure.

Try はモナドのコンテナ型で、例外を送出するか、計算した値を返す計算を表現します。
一見すると Either に似ていますが意味論的には異なります。
Try のインスタンスは、SuccessFailure のいずれかのインスタンスになります。

// no need to handle exceptions
Try.of(() -> bunchOfWork()).getOrElse(other);
import static io.vavr.API.*;        // $, Case, Match
import static io.vavr.Predicates.*; // instanceOf

A result = Try.of(this::bunchOfWork)
    .recover(x -> Match(x).of(
        Case($(instanceOf(Exception_1.class)), t -> somethingWithException(t)),
        Case($(instanceOf(Exception_2.class)), t -> somethingWithException(t)),
        Case($(instanceOf(Exception_n.class)), t -> somethingWithException(t))
    ))
    .getOrElse(other);

3.3.3. Lazy

Lazy is a monadic container type which represents a lazy evaluated value. Compared to a Supplier, Lazy is memoizing, i.e. it evaluates only once and therefore is referentially transparent.

Lazy はモナドのコンテナ型で、遅延評価する値を表現します。
Java の Supplier と違って、Lazy はメモ化により最初だけ評価して、それ以降は透過的に同じ値を参照するようになります。

Lazy<Double> lazy = Lazy.of(Math::random);
lazy.isEvaluated(); // = false
lazy.get();         // = 0.123 (random generated)
lazy.isEvaluated(); // = true
lazy.get();         // = 0.123 (memoized)

Since version 2.0.0 you may also create a real lazy value (works only with interfaces):

Vavr 2.0.0 からは、インターフェイスと組み合わせた場合だけ、本物の遅延評価を作ることができるようになりました。

CharSequence chars = Lazy.val(() -> "Yay!", CharSequence.class);

3.3.4. Either

Either represents a value of two possible types. An Either is either a Left or a Right. If the given Either is a Right and projected to a Left, the Left operations have no effect on the Right value. If the given Either is a Left and projected to a Right, the Right operations have no effect on the Left value. If a Left is projected to a Left or a Right is projected to a Right, the operations have an effect.

Either は2種類の型として解釈可能な値を表現します。
ある Either は Left と Right のいずれかになりうるということです。
ある Eigher に Left へ射影した Right の値を指定した場合、Right としての値に Left の操作を適用しても効果はありません。
ある Eigher に Right へ射影した Left の値を指定した場合、Left としての値に Right の操作を適用しても効果はありません。
Left には Left の値を、Right には Right の値を射影した場合のみ、それぞれの操作を適用した場合の効果が発生します。

Example: A compute() function, which results either in an Integer value (in the case of success) or in an error message of type String (in the case of failure). By convention the success case is Right and the failure is Left.

例えば、正常時は Integer を、それ以外ではエラーメッセージ(String)を返す compute() という関数があるとします。
慣習的に、正常時の返り値の型を Right、異常時の返り値の型を Left とすることになっています。

Either<String,Integer> value = compute().right().map(i -> i * 2).toEither();

If the result of compute() is Right(1), the value is Right(2).
If the result of compute() is Left("error"), the value is Left("error").

compute() の結果が Right(1) なら value の値は Right(2) になります。
compute() の結果が Left("error") なら value の値は Left("error") になります。

3.3.5. Future

A Future is a computation result that becomes available at some point. All operations provided are non-blocking. The underlying ExecutorService is used to execute asynchronous handlers, e.g. via onComplete(…​).

Future はある程度後になってから参照できるようになる計算結果です。
Future に対する全ての操作は実行フローを中断しません。
onComplete(…​) 等の非同期ハンドラを実行するため ExecutorService が使われています。

A Future has two states: pending and completed.

Future には保留(pending)と完了(completed)という2種類の状態があります。

Pending: The computation is ongoing. Only a pending future may be completed or cancelled.

Completed: The computation finished successfully with a result, failed with an exception or was cancelled.

保留(Pending) 計算を実行中です。保留の Future は完了(completed)あるいは中断(cancelled)のいずれかになります。

完了(Completed) 計算は完了しており、正常時はその結果を、異常時は例外を保持しています。中断(cancelled)している場合もあります。

Callbacks may be registered on a Future at each point of time. These actions are performed as soon as the Future is completed. An action which is registered on a completed Future is immediately performed. The action may run on a separate Thread, depending on the underlying ExecutorService. Actions which are registered on a cancelled Future are performed with the failed result.

Future には所定の時点に対応するコールバック操作を登録できます。
コールバックに登録した操作は Future が完了した時点ですぐに実行されます。
すでに完了した Future のコールバックに登録した操作はすぐに実行されます。
ExecutorService によっては、コールバックに登録した操作を別のスレッドで実行します。
中断した Future のコールバックに登録した操作は、失敗時の結果と共に実行されます。

// future *value*, result of an async calculation
Future<T> future = Future.of(...);

3.3.6. Validation

The Validation control is an applicative functor and facilitates accumulating errors. When trying to compose Monads, the combination process will short circuit at the first encountered error. But 'Validation' will continue processing the combining functions, accumulating all errors. This is especially useful when doing validation of multiple fields, say a web form, and you want to know all errors encountered, instead of one at a time.

Validation は アプリケイティブファンクタ およびエラーを蓄積する仕組みとして使われます。
モナドを合成するとき、合成プロセスは最初にエラーを検出した時点で短絡させます。
ですが、Validation は合成した全ての関数の実行を継続し、全てのエラーを蓄積します。
Web フォームのように複数のフィールドを検証するときや、全てのエラーを同時に確認したいときに役立つ機能です。

Example: We get the fields 'name' and 'age' from a web form and want to create either a valid Person instance, or return the list of validation errors.

Web フォームの 'name' フィールドと 'age' フィールドについて、正常な Person インスタンスを作成するか、検証エラーのリストを返したいときは次のようの記述します。

PersonValidator personValidator = new PersonValidator();

// Valid(Person(John Doe, 30))
Validation<Seq<String>, Person> valid = personValidator.validatePerson("John Doe", 30);

// Invalid(List(Name contains invalid characters: '!4?', Age must be greater than 0))
Validation<Seq<String>, Person> invalid = personValidator.validatePerson("John? Doe!4", -1);

A valid value is contained in a Validation.Valid instance, a list of validation errors is contained in a Validation.Invalid instance.

Validation.Valid のインスタンスには正しい値を格納します。
Validation.Invalid のインスタンスには検証エラーのリストを格納します。

The following validator is used to combine different validation results to one Validation instance.

次のコード例では複数の異なる Validation インスタンスを合成するバリデーターを定義しています。

class PersonValidator {

    private static final String VALID_NAME_CHARS = "[a-zA-Z ]";
    private static final int MIN_AGE = 0;

    public Validation<Seq<String>, Person> validatePerson(String name, int age) {
        return Validation.combine(validateName(name), validateAge(age)).ap(Person::new);
    }

    private Validation<String, String> validateName(String name) {
        return CharSeq.of(name).replaceAll(VALID_NAME_CHARS, "").transform(seq -> seq.isEmpty()
                ? Validation.valid(name)
                : Validation.invalid("Name contains invalid characters: '"
                + seq.distinct().sorted() + "'"));
    }

    private Validation<String, Integer> validateAge(int age) {
        return age < MIN_AGE
                ? Validation.invalid("Age must be at least " + MIN_AGE)
                : Validation.valid(age);
    }

}

If the validation succeeds, i.e. the input data is valid, then an instance of Person is created of the given fields name and age.

入力値が正しいとき、つまり検証が成功したら、nameage に値を設定した Person のインスタンスを生成します。

class Person {

    public final String name;
    public final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person(" + name + ", " + age + ")";
    }

}

3.4. コレクション

Much effort has been put into designing an all-new collection library for Java which meets the requirements of functional programming, namely immutability.

関数型プログラミングの要求を満たす、つまり、不変性を満たすように、Java の新しいコレクションライブラリを設計するのはとても大変でした。

Java’s Stream lifts a computation to a different layer and links to a specific collection in another explicit step. With Vavr we don’t need all this additional boilerplate.

Java の Stream は計算処理を別のレイヤーへ引き上げたり、コレクションと別の処理を結びつけたりするものでした。
Vavr では新たに定型的なコードを追加する必要がありません。

The new collections are based on java.lang.Iterable, so they leverage the sugared iteration style.

Vavr の提供するコレクションライブラリは java.lang.Iterable に基づいています。
ですので、イテレーションに関する糖衣構文を利用できます。

// 1000 random numbers
for (double random : Stream.continually(Math::random).take(1000)) {
    ...
}

TraversableOnce has a huge amount of useful functions to operate on the collection. Its API is similar to java.util.stream.Stream but more mature.

TraversableOnce はコレクションの操作に役立つさまざまな関数を持っています。
java.util.stream.Stream とよく似た API を提供していますが、より洗練されています。

3.4.1. リスト

Vavr’s List is an immutable linked list. Mutations create new instances. Most operations are performed in linear time. Consequent operations are executed one by one.

Vavr の List は不変連結リストです。
変更操作は新しいインスタンスを作成します。
ほとんどの操作は線形時間で完了します。
結果の生じる操作は1つずつ実行します。

Java 8
Arrays.asList(1, 2, 3).stream().reduce((i, j) -> i + j);
IntStream.of(1, 2, 3).sum();
Vavr
// io.vavr.collection.List
List.of(1, 2, 3).sum();

3.4.2. ストリーム

The io.vavr.collection.Stream implementation is a lazy linked list. Values are computed only when needed. Because of its laziness, most operations are performed in constant time. Operations are intermediate in general and executed in a single pass.

io.vavr.collection.Stream は遅延連結リストとして実装されています。
必要になったときだけ値を計算します。
遅延評価できるため、ほとんどの操作は定数時間で完了します。
ほとんどの操作には中間処理が介入するし、単一の経路で実行します。

The stunning thing about streams is that we can use them to represent sequences that are (theoretically) infinitely long.
ストリームに驚かされるのは、(理論的に)無限長のシーケンスを表現できることです。

// 2, 4, 6, ...
Stream.from(1).filter(i -> i % 2 == 0);

3.4.3. 性能特性

Table 1. 順次処理の時間計算量(Time Complexity of Sequential Operations)
head() tail() get(int) update(int, T) prepend(T) append(T)

Array

定数

線形

定数

定数

線形

線形

CharSeq

定数

線形

定数

線形

線形

線形

Iterator

定数

定数

List

定数

定数

線形

線形

定数

線形

Queue

定数

(償却あり)定数

線形

線形

定数

定数

PriorityQueue

対数

対数

対数

対数

Stream

定数

定数

線形

線形

(遅延評価)定数

(遅延評価)定数

Vector

(実質的に)定数

(実質的に)定数

(実質的に)定数

(実質的に)定数

(実質的に)定数

(実質的に)定数

Table 2. マップ、セット処理の時間計算量(Time Complexity of Map/Set Operations)
contains/Key add/put remove min

HashMap

(実質的に)定数

(実質的に)定数

(実質的に)定数

線形

HashSet

(実質的に)定数

(実質的に)定数

(実質的に)定数

線形

LinkedHashMap

(実質的に)定数

線形

線形

線形

LinkedHashSet

(実質的に)定数

線形

線形

線形

Tree

対数

対数

対数

対数

TreeMap

対数

対数

対数

対数

TreeSet

対数

対数

対数

対数

セルの値の意味は以下のとおり。

  • 定数 — 定数時間

  • (償却あり)定数 — 一部の処理により多くの時間はかかるけど、大半は定数時間

  • (実質的に)定数 — ハッシュキーの分布に依存するけど実質的に定数時間

  • (遅延評価)定数 — 操作は遅延処理されるので定数時間

  • 対数 — 対数時間

  • 線形 — 線形時間

3.5. プロパティチェック

Property checking (also known as property testing) is a truly powerful way to test properties of our code in a functional way. It is based on generated random data, which is passed to a user defined check function.

プロパティチェック( プロパティテスト とも呼ばれている)は、関数型プログラミングにおける、自分で書いたソースコードの特徴を検証する強力は手法です。

Vavr has property testing support in its io.vavr:vavr-test module, so make sure to include that in order to use it in your tests.

Vavr の io.vavr:vavr-test モジュールはプロパティテストに対応しているので、テストに導入するのは簡単です。

Arbitrary<Integer> ints = Arbitrary.integer();

// square(int) >= 0: OK, passed 1000 tests.
Property.def("square(int) >= 0")
        .forAll(ints)
        .suchThat(i -> i * i >= 0)
        .check()
        .assertIsSatisfied();

Generators of complex data structures are composed of simple generators.

単純なジェネレーターを合成すれば、複雑なデータ構造のジェネレーターを構築できます。

3.6. パターンマッチ

Scala has native pattern matching, one of the advantages over plain Java. The basic syntax is close to Java’s switch:

Scala は言語としてパターンマッチングに対応しているところが、素の Java に対する利点の1つになっています。
基本的な構文は Java の switch 文によく似ています。

val s = i match {
  case 1 => "one"
  case 2 => "two"
  case _ => "?"
}

Notably match is an expression, it yields a result. Furthermore it offers

  • named parameters case i: Int ⇒ "Int " + i

  • object deconstruction case Some(i) ⇒ i

  • guards case Some(i) if i > 0 ⇒ "positive " + i

  • multiple conditions case "-h" | "--help" ⇒ displayHelp

  • compile-time checks for exhaustiveness

match は式として値を返すところが重要です。次のように記述できます。

  • 名前付きパラメータ case i: Int ⇒ "Int " + i

  • オブジェクトの解体 case Some(i) ⇒ i

  • ガード節 case Some(i) if i > 0 ⇒ "positive " + i

  • 複数の条件式 case "-h" | "--help" ⇒ displayHelp

  • コンパイル時の網羅性チェック(exhaustiveness)

Pattern matching is a great feature that saves us from writing stacks of if-then-else branches. It reduces the amount of code while focusing on the relevant parts.

パターンマッチングは if-then-else の条件分岐を無駄に積み重ねるのを減らしてくれる素敵な機能です。
コード量を減らすことで、重要な部分に注力できるからです。

3.6.1. Java におけるパターンマッチの基本

Vavr provides a match API that is close to Scala’s match. It is enabled by adding the following import to our application:

Vavr は Scala の match によく似た match API を提供します。
次のような import 文を追加するだけで利用できるようになります。

import static io.vavr.API.*;

Having the static methods Match, Case and the atomic patterns

  • $() - wildcard pattern

  • $(value) - equals pattern

  • $(predicate) - conditional pattern

そうすると MatchCase といった静的メソッドや、次のような _atomic パターン が使えるようになります。

  • $() - ワイルドカードパターン

  • $(value) - 等値パターン

  • $(predicate) - 条件式パターン

in scope, the initial Scala example can be expressed like this:

前に説明した Scala のコード例は次のように記述できます。

String s = Match(i).of(
    Case($(1), "one"),
    Case($(2), "two"),
    Case($(), "?")
);

⚡ We use uniform upper-case method names because 'case' is a keyword in Java. This makes the API special.

⚡ Java では 'case' が予約語になっているため、メソッド名は大文字で始める形式に統一しています。APIとして分かりやすくなっていると思います。

網羅性チェック

The last wildcard pattern $() saves us from a MatchError which is thrown if no case matches.

ワイルドカードパターン $() を使う限り、マッチしなかったときに MatchError を送出しないようになります。

Because we can’t perform exhaustiveness checks like the Scala compiler, we provide the possibility to return an optional result:

Scala コンパイラのように網羅性をチェックしないので、Option を返す可能性を表現するようにしています。

Option<String> s = Match(i).option(
    Case($(0), "zero")
);
糖衣構文

As already shown, Case allows to match conditional patterns.

Case を使うと条件式パターンにマッチさせることができます。

Case($(predicate), ...)

Vavr offers a set of default predicates.

Vavr の定義済み述語式を使うには次のような import 文を追加します。

import static io.vavr.Predicates.*;

These can be used to express the initial Scala example as follows:

Vavr の定義済み述語式を使うと、最初の Scala のコード例は次のように記述できます。

String s = Match(i).of(
    Case($(is(1)), "one"),
    Case($(is(2)), "two"),
    Case($(), "?")
);

複数の条件

We use the isIn predicate to check multiple conditions:

isIn 述語式は複数の条件をチェックできます。

Case($(isIn("-h", "--help")), ...)

副作用を引き起こす

Match acts like an expression, it results in a value. In order to perform side-effects we need to use the helper function run which returns Void:

Match は式なので値を返します。
副作用を引き起こすには、Void を返すヘルパー関数 run を使わなければなりません。

Match(arg).of(
    Case($(isIn("-h", "--help")), o -> run(this::displayHelp)),
    Case($(isIn("-v", "--version")), o -> run(this::displayVersion)),
    Case($(), o -> run(() -> {
        throw new IllegalArgumentException(arg);
    }))
);

run is used to get around ambiguities and because void isn’t a valid return value in Java.

run を使うのは曖昧性を解消するためです。なぜなら、Java では void が正しい返り値の型ではないからです。

Caution: run must not be used as direct return value, i.e. outside of a lambda body:

注意点: ラムダ式の外側で、直接値を返すために run を使ってはいけません。

// Wrong!
Case($(isIn("-h", "--help")), run(this::displayHelp))

Otherwise the Cases will be eagerly evaluated before the patterns are matched, which breaks the whole Match expression. Instead we use it within a lambda body:

Case の評価は貪欲評価です。
つまりパターンを マッチさせる前 に評価します。
その結果、マッチ式全体が壊れてしまう場合もあります。
そういうときはラムダ式の内側で run を使います。

// Ok
Case($(isIn("-h", "--help")), o -> run(this::displayHelp))

As we can see, run is error prone if not used right. Be careful. We consider deprecating it in a future release and maybe we will also provide a better API for performing side-effects.

ここまでに見てきた通り、 run は間違った使い方をしやすい機能です。
将来のリリースでは廃止して、副作用を引き起こすためのより適切な API を提供できないか検討するつもりです。

名前付きパラメータ

Vavr leverages lambdas to provide named parameters for matched values.

Vavr はパターンマッチングで名前付きパラメータを実現するため、ラムダ式を利用しています。

Number plusOne = Match(obj).of(
    Case($(instanceOf(Integer.class)), i -> i + 1),
    Case($(instanceOf(Double.class)), d -> d + 1),
    Case($(), o -> { throw new NumberFormatException(); })
);

So far we directly matched values using atomic patterns. If an atomic pattern matches, the right type of the matched object is inferred from the context of the pattern.

ある程度の複雑さになるまでは atomic パターンで直接値を記述できます。
atomic パターンがマッチすれば、パターンのコンテキストから適切な型のマッチオブジェクトを推論します。

Next, we will take a look at recursive patterns that are able to match object graphs of (theoretically) arbitrary depth.

次は、(理論的には)任意の深さのオブジェクトグラフにマッチできる再帰的なパターンについて説明します。

オブジェクトの解体

In Java we use constructors to instantiate classes. We understand object decomposition as destruction of objects into their parts.

Java ではコンストラクタでクラスのインスタンスを作成します。
オブジェクトの解体 とは、オブジェクトを複数の部品へ分解するということです。

While a constructor is a function which is applied to arguments and returns a new instance, a deconstructor is a function which takes an instance and returns the parts. We say an object is unapplied.

複数の引数を 適用し 新しいインスタンスを返す 関数 がコンストラクタだとすると、デコンストラクタはインスタンスを引数として、複数の値を返す関数だと考えられます。
これを、オブジェクトを 逆適用した(unapplied) と呼びます。

Object destruction is not necessarily a unique operation. For example, a LocalDate can be decomposed to

  • the year, month and day components

  • the long value representing the epoch milliseconds of the corresponding Instant

  • etc.

オブジェクトの解体は必ずしも単独の操作で実現しなければならないわけではありません。
例えば、LocalDate は次のように分解できるでしょう。

  • 年、月、日、それぞれのコンポーネント

  • インスタントに対応する long のエポックミリ秒

  • それ以外

3.6.2. さまざまなパターン

In Vavr we use patterns to define how an instance of a specific type is deconstructed. These patterns can be used in conjunction with the Match API.

Vavr では特定の型のインスタンスをどのように解体するのか説明するパターンを定義します。
これらのパターンは Match API と組み合わせて使用できます。

定義済みのパターン

For many Vavr types there already exist match patterns. They are imported via

Vavr のほとんどの型には定義済みのパターンが存在します。
次のような import 文を追加すれば使用できるようになります。

import static io.vavr.Patterns.*;

For example we are now able to match the result of a Try:

例えば Try の結果は次のようにマッチできます。

Match(_try).of(
    Case($Success($()), value -> ...),
    Case($Failure($()), x -> ...)
);

⚡ A first prototype of Vavr’s Match API allowed to extract a user-defined selection of objects from a match pattern. Without proper compiler support this isn’t practicable because the number of generated methods exploded exponentially. The current API makes the compromise that all patterns are matched but only the root patterns are decomposed.
⚡ Vavr の最初のプロトタイプに含まれる Match API では、ユーザーの定義したフィールドを抽出できるのはパターンにマッチしたオブジェクトからだけでした。生成するメソッドが指数関数的に増加してしまうため、適切なコンパイラの支援がなければ実用的ではありませんでした。現在の API はその辺を妥協して全てのパターンにマッチはするけど親の(root の)パターンだけを 解体 するようになっています。

Match(_try).of(
    Case($Success(Tuple2($("a"), $())), tuple2 -> ...),
    Case($Failure($(instanceOf(Error.class))), error -> ...)
);

Here the root patterns are Success and Failure. They are decomposed to Tuple2 and Error, having the correct generic types.

この場合親の(root の)パターンは SuccessFailure です。
それぞれより一般的な型の Tuple2Error へ分解できます。

⚡ ネストした型を深くまで推論するのはマッチされた引数であって、マッチしたパターン ではありません

ユーザー定義パターン

It is essential to be able to unapply arbitrary objects, including instances of final classes. Vavr does this in a declarative style by providing the compile time annotations @Patterns and @Unapply.

たとえそれが final クラスのオブジェクトであっても、オブジェクトを逆適用できるようにするのは必要不可欠です。
Vavr では、コンパイル時に処理するアノテーション @Patters@Unapply で宣言的に記述できるようにしました。

To enable the annotation processor the artifact vavr-match needs to be added as project dependency.

アノテーションプロセッサを有効化するには vavr-match をプロジェクトの依存ライブラリに追加しなければなりません。

⚡ 注意点:当然ですが、パターンはコードジェネレータを使わなくても直接実装できます。詳しい内容については自動生成されたソースコードを参照してくdさい。

import io.vavr.match.annotation.*;

@Patterns
class My {

    @Unapply
    static <T> Tuple1<T> Optional(java.util.Optional<T> optional) {
        return Tuple.of(optional.orElse(null));
    }
}

The annotation processor places a file MyPatterns in the same package (by default in target/generated-sources). Inner classes are also supported. Special case: if the class name is $, the generated class name is just Patterns, without prefix.

アノテーションプロセッサは同じパッケージにファイル MyPatterns を作成します(デフォルトの作成位置は target/generated-sources です)。
内部クラスにも対応しています。
ただし、クラス名が $ の場合、自動生成されたクラス名はただの Patterns になります。前置詞は付きません。

ガード節

Now we are able to match Optionals using guards.

Option に対するマッチを ガード節 として記述できます。

Match(optional).of(
    Case($Optional($(v -> v != null)), "defined"),
    Case($Optional($(v -> v == null)), "empty")
);

The predicates could be simplified by implementing isNull and isNotNull.

これらの述語式は isNullisNotNull を実装すれば簡潔にできます。

⚡ And yes, extracting null is weird. Instead of using Java’s Optional give Vavr’s Option a try!
⚡ もちろん、null の抽出にも結びついています。Java の Optional ではなく Vavr の Option を試してみましょう。

Match(option).of(
    Case($Some($()), "defined"),
    Case($None(), "empty")
);

4. License

Copyright 2014-2018 Vavr, http://vavr.io

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.