オラクルのJava言語アーキテクトであるBrian Goetz氏と、オラクルのプログラミング言語研究者であるGavin Bierman氏の2人はJavaプログラミング言語への統合を見込んだパターンマッチの構想についてInfoQに述べた。

動機

この研究の動機は、いくつかの一般的なJavaプログラミングのイディオムを改善することだ。以下のことを考えてみよう。

if (obj instanceof Integer) { int intValue = ((Integer) obj).intValue(); // use intValue }

動作中3つの操作がある。

obj が Integer 型かどうかを特定するための検査

が 型かどうかを特定するための検査 obj を Integer 型にキャストする変換

を 型にキャストする変換 Integer から int を取り出す分解操作

次に if...else 構造にある他のデータ型に対して検査することを考えてみよう。

String formatted = "unknown"; if (obj instanceof Integer) { int i = (Integer) obj; formatted = String.format("int %d", i); } else if (obj instanceof Byte) { byte b = (Byte) obj; formatted = String.format("byte %d", b); } else if (obj instanceof Long) { long l = (Long) obj; formatted = String.format("long %d", l); } else if (obj instanceof Double) { double d = (Double) obj; formatted = String.format(“double %f", d); } else if (obj instanceof String) { String s = (String) obj; formatted = String.format("String %s", s); } ...

上記のコードは一般的に使用されており簡単に理解できる一方、退屈なものだ(ポイラープレートコードの繰り返し)。そしてバグが潜みそうな場所を多く提供している。過剰なボイラープレートはまたビジネスロジックを見えなくすることがよくある。たとえば一度 instanceof がインスタンスのデータ型を確認してしまえば、キャストは不必要で退屈に思える。

Goetz氏とBierman氏は彼らが提案している改善の全体的な意図を説明している。

アドホックな解決策への研究というよりむしろ、私たちはJavaのためにパターンマッチングを受け入れるときだと考えています。パターンマッチングは多くの異なるプログラミング言語のスタイルで採用されてきた技術です。1960年代までさかのぼります。ここにはSNOBOL4やAWKといったテキスト指向の言語やHaskellやML、直近ではオブジェクト指向言語を拡張したScala(より最近ではC#)といった関数型言語を含みます。 パターンは述語の組み合わせです。もし述語が対象に適用されるなら、対象から取り出されたバインドしている変数一式とともに対象に適用されます。

Goetz氏とBierman氏は matches や exprswitch といった新しいキーワードを含むさまざまなパターンで実験してきた。

matches オペレータ

提案された matches オペレータでは instanceof 検証が不要となる。たとえば、

if (x matches Integer i) { // use i here }

バインドしている変数、 i は変数 x が Integer にマッチするときだけ使用される。 if...else 構造内にある他のデータ型を含める上記の拡張は不必要なキャストを削除する。

switch の改善

Goetz氏とBierman氏は“ switch 文はパターンマッチングに完全に“マッチ”すると説明する。たとえば、

String formatted; switch (obj) { case Integer i: formatted = String.format("int %d", i); break; case Byte b: formatted = String.format("byte %d", b); break; case Long l: formatted = String.format("long %d", l); break; case Double d: formatted = String.format(“double %f", d); break; default: formatted = String.format("String %s", s); } ...

上記のコードはより簡単に読め、散らかっていない。しかし、Goetz氏とBierman氏は switch の限界を指摘する。“これは文であり、それゆえにcase部もまた文でなければならない。私たちは3値の条件演算子の一般化である式の形態にしたい。ここではN個の式のうちまさに1つだけが評価されることが保証される。”

彼らの解決策は、新しい式の文である exprswitch の提案である。

String formatted = exprswitch (obj) { case Integer i -> String.format("int %d", i); case Byte b -> String.format("byte %d", b); case Long l -> String.format("long %d", l); case Double d -> String.format(“double %f", d); default -> String.format("String %s", s); }; ...

Goetz氏とBierman氏が提案したパターンの要約は次のことを含む。

型検証パターン(キャストの対象をバインドしている変数に束縛する)

分解パターン(対象を分解しサブパターンにコンポーネントを再帰的にマッチさせる)

定数パターン(同値でマッチさせる)

Varパターン(すべてでマッチさせ対象にバインドする)

_パターン(すべてにマッチさせる)

Goetz氏はパターンマッチングについてInfoQに語った。

InfoQ「あなたが提案を公開してから、コミュニティからの反応はどんなものでしたか？」

Goetz氏「とても良い反応でした。他の言語でパターンマッチングを使ったことがある人は実にそれが好きです。それがJavaに来ることをとても喜んでいます。まだ見たことがない人に対しては、なぜ言語にこれを追加することが重要だと考えているのかということに関してより広める努力をしていくつもりです。」

InfoQ「Scalaのmatch演算子の設計がこの設計に今までどのくらい影響しましたか？Scalaのmatchができることの中で、Javaのパターンマッチができないであろうことは具体的にありますか？」

Goetz氏「Scalaは単にJavaでパターンマッチがどうあるべきかということについてアイデアを与えてくれる多くの言語の1つです。言語に機能を追加することは単にある他の言語からその機能を"ポーティング"するということではありません。もしそうすれば、横に釘が打たれたバッグのように見えるでしょう。私たちはScalaのパターンマッチングができるすべてをせずに終わる可能性が高いです。またScalaがしていないことをいくつかするでしょう。 パターンマッチングをScalaがやっているよりもより深くオブジェクトモデルに統合する機会があると考えています。Scalaのパターンは実質的に静的なものです。それらは簡単にオーバーロードやオーバーライドできません。まだとても有効ですが、私たちはもっとよくできると考えています。 分解は構築の裏面です。ちょうどOO言語があなたにオブジェクトを構築する方法に対して選択肢(コンストラクタ、ファクトリ、ビルダ)を与えてくれるように、分解においても同様の選択肢を持つことでより豊かなAPIとなると考えています。パターンマッチングは歴史的に関数型言語に結びついていますが、OOにも正当な場所があると考えています。単に歴史的に見落とされてきたものなのです。 言語機能について興奮するというのは簡単です。しかし言語機能は本来よりよいライブラリを可能にするものであるべきだと考えています。なぜならライブラリの豊かなエコシステムを持つということにおいて、かなりの影響力があるからです。パターンマッチングは間違いなくよりシンプルで安全にライブラリを書くことを可能にしてくれます。 パターンマッチングを必要とすると認識されたことがなかったOOライブラリの例として、 java.lang.Class にあるメソッドの組を考えてみましょう。 public boolean isArray() { ... } public Class getComponentType() { ... } 2番目のメソッドは前提条件があります。最初のメソッドがtrueを返すということです。複数のAPIポイントにわたって広がっている操作ロジックがあると、API記述者(より多く規定し、より多くテストしなければならない)にとって複雑になり、ユーザ(間違いやすくなる)にとってもそうなります。必然的に、これら2つのメソッドは1つのパターンです。そのパターンは"このクラスは配列クラスを表しているか"という妥当性テストとコンポーネントの型の条件抽出が必要です。もし実際にそれがそのように表現しているなら、より簡単に書け、誤って使うことができないものとなるでしょう。」 if (aClass matches Class.arrayClass(var componentType)) { ... }

InfoQ「開発中のScalaがJavaのバージョンの上にmatchをリベースできるように実装技術を提供することは目標ですか、それともそうではありませんか(Scala 2.12がインタフェースの上にトレイトをリベースしたのと同じように)？」

Goetz氏「そうしたいと思っているのですが、ラムダと同様にこの機能の設計過程では基盤プラットフォームの一員となる実用的な構築ブロックを特定するつもりです。そこから複数の言語が利点を得られるようなものです。複数言語間でより相互運用性を高めるものを提供したいです。」

InfoQ「Scalaの実装では、かなりの量の追加となる合成バイトコードが生成されています。ケースクラスの分解のような機能をサポートするためです。VMやバイトコード構造へ同じような量を追加することは何か欠点がありますか？」

Goetz氏「リスクはコンパイラが通常の役割以外のことに足を踏み入れ、通常開発者の制御下にあるクラスメンバにセマンティクスを割り当ててしまうことです。便利である一方で、つねにユーザが望むものというわけではありません。これはしばしば生成を微調整する道具への要望につながります(たとえば Object.equals() より Arrays.equals() を使って配列を比較するといったこと）。」

InfoQ「分解はデータクラスに限られますか？」

Goetz氏「私たちはパターンマッチングを複数回のリリースに分けることを計画しています。初めは型検証のような単純なパターンで、それからデータクラスでの分解パターン、最終的にはユーザが自由にかける分解パターンと考えています。ゆえに分解をデータクラスに限るということは意図していません。このことが真実となるのは、一時的なことでしょう。」

InfoQ「データクラスとバリュータイプの関係を話してもらえませんか？」

Goetz氏「この2つはほとんど完全に直行するものです。値は一意性を持たない集合についてのものです。明確に一意性を否定することで、ランタイムはメモリ内のレイアウトを最適化できます。間接参照とオブジェクトヘッダから離れてフラットにし、より自由に同期地点をまたがって値のコンポーネントをキャッシュします。データクラスは複雑性やクラス表現とそのAPI契約間の間接的な関係性についてのものです。そうすることで、コンパイラはコンストラクタやパターンマッチャ、 equals 、 hashCode 、 toString といった共通のクラスメンバを書き込むことができます。クラスはバリュークラスやデータクラス、またはどちらでもないものや両方に該当するものに使用できるでしょう。組み合わせすべてに適するものです。」

InfoQ「密閉するためにはソースコードコンパイラからのサポートがいくらか必要です。」

Goetz氏「密閉すること(finalにすることのような)はコンパイラからのサポートを要求するだけでなく、理想的にはJVMからのサポートも得るでしょう。言語レベルの制限、"XはYを継承できない"といったことがJVMによって強制されているためです。」

InfoQ「密閉の意味は"このモジュールの外ではサブクラスではない可能性がある"といったことですか？」

Goetz氏「密閉が意味することには範囲があります。もっとも単純な形は"同じソースファイル内で宣言される他のクラスのみ継承できる"ということを意味するでしょう。これは密閉のよくある解釈であるだけでなく、別々のコンパイル経由で同期することから抜け出せないため、非常に単純でもあります。密封は"同一パッケージ"または"同一モジュール"を意味すると定義することもできます。よりおかしくして、"既知"のリストや複雑な実行条件で許可するとすることもできます。私たちはほぼ確実にこの範囲の単純な側に着地するでしょう。もっとも単純な解釈の向こうにある複雑性がもたらすものはすぐになくなっていきます。」

InfoQ「6ヶ月という新しいJavaのリリースサイクルは言語にパターンマッチングを統合することをうまく促進しますか？」

Goetz氏「そう願っています！すでにパターンマッチングを分割して個々の実用的な"かたまり"にしています。比較的早く単純なパターンのサポートを提供できるようにするためです。その上に構築し続けます。」

InfoQ「プロトタイプを先進的なユーザが使用できるようになるのはどの時点でしょうか？」

Goetz氏「すでにそうなっています。ソースからJDKをコンパイルする意志があるアーリーアダプタにとっては。"Amber" forestにブランチがあります。switchと"matches"の述語で型検証のパターンをサポートしています。」

InfoQ「パターンマッチングの研究で何が見え始めていますか？」

Goetz氏「クラスメンバとしてマッチャを仕上げたいので、その方法をまだ探究しています。またそれらがオーバーロードと継承でどのように関わるのかといった疑問も探求しています。ここで解明すべきことはたくさんあります。」

リソース