Project Valhalla 2023

2023/03/30 にやったJava仕様勉強会の動画が公開されました。当日はJavaのマスコットDuke風の服で臨みました(どうでもいい裏情報)

www.youtube.com

セッション資料もアップロードしたので参考にしてください。

Project Valhalla 2023 中間報告

いずれも 2023年3月時点での情報です。JEPもドラフト版だったりするので、将来的に変更が入る可能性が高いことをお断りしておきます。本稿では勉強会のセッション内容に加えて、セッション時点で追従できていなかった変更点や、勉強会での指摘を踏まえてフォローアップした内容を含みます。

もしもValhalla世界でJava入門したら

ここでは、Valhalla導入後のJava世界だと入門者視点でどのように変わるのかというアプローチをしています。まず、Javaのデータ型は大きくふたつに分類できて、Identity Objects と Value Objects です。

nullの使用 不変性 同値判定
Identity Objects Record 不変 equals
Class 可変 equals
Interface 可変 equals
Array 可変 Arrays.deepEquals
Enum* 不変 ==
Value Objects Record 不変 ==
Class 不変 ==
Primitive 不可 不変 ==

どう違うの?というと割と難しくて、パッと見は大きな違いを感じないかもしれません。
言語的な定義を言ってしまえば、 class宣言で identity class と宣言されていればそのインスタンスは Identity Objects ですし、value class と宣言されていればそのインスタンスValue Objects です。でも、そんな説明では納得されないことでしょう。

Value Objects はメモリ上に直接展開可能なデータで、メモリレイアウトを効率化することができます。

しかしこれは、あくまでJavaVMの中の話で、プログラミング言語Javaを扱うプログラマー視点で見た場合、オブジェクトの可変性 / 不変性という特性や、同値判定をする際に equalsメソッドを使うか、==演算子を使うかという形でしか実感しにくいものであろうと思います。

Valhallaのスローガンとして出てくる

Codes like a class, works like an int.

クラスのように書き、intのように動く、という標語は、反面、その違いを分かりにくいものとしています。

まあ、ライトユースでは雰囲気でコードを書いてもだいたいいい感じになるんじゃないかな。

プリミティブ型の位置づけ

プリミティブ型は Value Objectsの一部という位置づけになります。
プリミティブ型については JEP 402: Enhanced Primitive Boxing (Preview) にて検討されています。このJEP 402で目を引くのは以下の部分でしょうか。

int i = 12;
int iSize = i.SIZE;
double iAsDouble = i.doubleValue();
Supplier<String> iSupp = i::toString;

int 型変数に対して、あたかも普通の参照型のようにメソッド呼び出しをしているかのように記述することができます。これは、裏の仕組みとしてはラッパー型のIntegerの該当メソッドを呼ぶような仕組みを想定しているようですが、これにより言語の表面上はプリミティブ型も参照型も同じように扱えるように見えるのではないでしょうか。

まあ演算子で操作できるのはプリミティブ型(と一部のクラス)の特権という感じでしょうけども。

ジェネリクスでも List<int>という記述が可能なように拡張しようとしています。こちらの裏の仕組みは後述します。

Valhalla のやさしい世界

このように、入門者からみたValhallaは、プリミティブ型も参照型もあまり意識せずに雰囲気で扱えるやさしい世界のように見えるかもしれません。
基本的に Value Objects を使いなさい、特別な必要がなければ不変性のデータ型を使うのです、という規範が普及することでしょう。そしてValue Objects を中心に使っているならば副作用(※専門用語)に起因するバグにも悩まされずに済む。==でオブジェクト比較してもバグの原因になったりもしないわけです。

ついでにパフォーマンスも、普通に使っている分には感知できないでしょうが、きっと改善する。

JavaVM の大改造

Valhalla後のJavaを表層的に見れば、言語がリファクタリングされて綺麗になったね、めでたしめでたしといったところなのですが、その裏方は地獄です。

Project Valhalla は 2014年に発表されました。Java8がリリースされた後のJavaOne 2014 San Franciscoが舞台です。当時の様子は櫻庭さんの記事を参照してください。

2023年4月現在参照可能な JavaVM の Valhalla での変更点の解説セッションは JVM Language Summit 2019 のものが分かりやすいでしょうか。

2014年に発表されてすでに2023年ですから随分と時間がかかっていますね。たくさんのプロトタイピングとたくさんの教訓から掘り下げているといったことが語られています。

Early Access Release

Valhalla の Java VM のプロトタイプ版がリリースされています。2023年4月現在のバージョンはLW-4 となっています。ダウンロードして動かすこともできますが、主にJava VM側の実証プロトタイプで、プログラミング言語側のUniversal Genericsといった面白い部分は未実装です。jshellも制限があるようなので、機能を確認したい場合はjavacでコンパイルしてjavaで実行した方が良さそうです。

JavaVM のプロトタイピング

Valhalla は既存の JavaVM に対して大きなインパクトの修正を加える必要が生じます。そのため、まずプロトタイプを開発して検証を行いました。最初のプロトタイプが "Q-World" と呼ばれるものです。この"Q"とはValue型だけを表す型のことで、既存のプリミティブ型や参照型とは別に独立のValue型を作るという方針のものでした。これは言わば「ユーザー定義のプリミティブ型を作る」といったアプローチです。

このプロトタイプは機能することはしたのですが、プロトタイプを実装することで問題もまた見えてきました。Q-Worldの問題点として

  • プリミティブ型と参照型の分断をさらに大きくする
  • 加えて更なるボクシングも必要になる
  • 苦痛を伴うマイグレーション
  • 苦痛を伴う特殊化。配列は特に痛い

といったものが挙げられています。

そこで方針を転換して作られたプロトタイプが "L-World" というものです。この"L"とは既存の参照型のことで、しばしばスタックトレースなどで見かける LJava/lang/Object といった型表現の先頭の"L"です。つまり、Value型をjava.lang.Objectを継承した型として扱うアプローチです。

  • バイトコードは既存の a* を用いる (これはaload, astore といったバイトコードを指しています)
  • java.lang.Objectを継承した型として扱う
  • Value型の配列は Object[] の継承型として扱う
  • プログラム言語のモデルはよりよくなるが、VMへのインパクトはより大きくなる

といったことが先の動画で解説されています。

互換性

Project Valhalla では既存のJavaとの互換性を維持する方針が示されています。この背景については Background: How We Got the Generics We Have という文章があります。これは2004年にリリースされたJava5のときに、いかにして互換性を保ちながらジェネリクスを導入したのか?ということについて書かれています。この文章はとてもエモいと私は思っていて、機会を改めて解説したいところですが、要点を超訳すると

Goal: Gradual migration compatibility
ゴール: 段階的な移行の互換性

ということになります。つまり、バイナリ互換性のない分断された言語仕様追加では、その断絶の前後で、ソースコードになんの変更がなくとも、すべてのクラスを一斉に再コンパイルする「フラグの日」が必要になります。

java.util.ArrayListのようなコアAPIのクラスを変更するならば、世界中全てのJavaコードを一斉に再コンパイルする必要があります。あるいは、Java 1.4 まで用のクラスとして永遠にとどめおく必要があります。

Javaは動的リンク、つまり実行時にJavaVMによって外部のjarファイルが読み込まれたりする動きをしますから、この「フラグの日」というのはより強い嫌悪感をもたらしました。(補足するなら当時はまだJava Appletが活きていましたし、Java VM 間のRMIによる呼び出しのようなものも活きていてネットワークを介して複数のJavaVMが協調動作するような想定も強く存在していました)

そうした20年前の事例を踏まえて、Valhallaでも互換性を保ちながら、徐々に移行する道が選ばれたのです。

Valhalla のJEPs

Project Valhallaの中核となるJEPは4つあります。

セッションをやった当日(2023年3月30日)の朝に参照していたJEPのタイトルが変更になるハプニングがありました。本稿執筆時点で再度確認しているのですが、また名前が変わっている……。現時点で活発に更新がされていることの証左でもありましょう。過去の資料を照会する時には名称変更にお気を付けください。

JEP401, JEP402 は既にJEPの番号がついていますが、 Value Objects や Universal Generics はまだドラフト版で正式なJEPが発番されていません。Java VM 部分が固まってきたため、ようやく上モノの言語仕様が進むようになってきたと見るべきでしょうか。

また、関連JEPとして以下のものがValhalla公式ページには挙げられています。

これらはリリース済みのものが多くあります。こうした部分でも徐々に進んでいることが伺えるでしょう。

型の再整理

Valhalla では既存のプリミティブ型と参照型という枠組みを、再整理してJava言語をリファクタリングしようという試みをしています。


この図は The Language Model というBackground Documentsで出てくるのですが、上の図の既存のJavaのデータ型をValhallaでは下の図のようにしますよ、という話です。

先に挙げた表を再掲しますが

nullの使用 不変性 同値判定
Identity Objects Record 不変 equals
Class 可変 equals
Interface 可変 equals
Array 可変 Arrays.deepEquals
Enum* 不変 ==
Value Objects Record 不変 ==
Class 不変 ==
Primitive 不可 不変 ==

既存の型を大きく Identity Objects と Value Objects に再分類しようとしています。なお、Enumについては理論的にはValue型にできるものですが、java.lang.Enumを継承する構成上、Identity Objects になっているという注釈がありました。

Record については Identity にも Value にもなれますが、いずれも不変です。フィールドにIdentity を含んでいると Identity にせざるを得ないといった面倒くさい考慮事項があります。

配列は "L-World" では既存の配列の性質を引き継いでいます。配列の仕様にまつわる型システム的な不都合がいろいろとあるのですが、そのあたりは互換を維持する方向で梃入れはしない方針のようです。

プリミティブ型は Value Objects のひとつという位置に据えられ、プリミティブ型と参照型の分断を軽減しようと試みられています。

Value Objects

クラス宣言に value 識別子を加えて宣言するとそのクラスのインスタンスValue Objects になります。
クラス宣言に identity 識別子を加えて宣言するとそのクラスのインスタンスは Identity Objects になります。(既存の参照型を使いたい場合はこちら)

Value Objectの利点としては == で値比較ができるようになる点が挙げられます。

また、value 宣言した場合の主な制約は

  • クラスは暗黙にfinalとなり継承できなくなります
  • すべてのフィールドは暗黙にfinalとなり、コンストラクター(ないし初期化子)で1度だけ値を設定しなければなりません
  • identityクラスを拡張またはidentity インターフェースを実装して Valueクラスを作ることはできません
  • コンストラクターでthisやsuperを使うことはできません。インスタンスの作成は、スーパークラスの初期化コードを実行せずに行われます
  • synchronized メソッドを宣言できません
  • finalize() メソッドを宣言できません(おそらく)
  • コンストラクタではthisを参照できません(例外事項があるがここでは省略)

といったものとなっています。これらは今後、言語仕様を詰めていく段階で変更になる可能性があります。

既存の value ないし identity 識別子のついていないコードは、およそ identity クラスになると思ってよいようです。(例外事例があるようなのですが複雑でよくわかりません……)

このあたりは言語仕様の差分に詳細な記載があります。the specification changes がリンク切れなので少し古い版へのリンクを貼っておきます。 8.1.1.5 identity and value Classes

JEP 401: Implicit Value Object Creation

"Q-World"のような古いValhallaの提案では、ユーザー定義のプリミティブ型を作成できるような提案となっていました。現在の "L-World" になってこの提案は大きく修正されたように思います。

JEP 401 はざっくりいえば、Value型のデフォルト値の動きを規定するようなJEPで、

public implicit Range();

といったように implicit キーワードをつけたデフォルトコンストラクタを記述することで、各フィールドをデフォルト値としたValue型のインスタンスを生成します。(構文はあくまで仮のものです)

また、 Null-Restricted and Nullable Types (Preview) に依存するのですが、nullを許容しない型についての検討があるようです。しかし、このJEPはドラフト 8303099 なのですがリンク切れになっており、おそらく再検討されているのではないかと思います。

JEPは消えていますが、何が書いてあったかというと ValhallaのML に記載があったので紹介しておきます。

'Foo!' refers to a non-null Foo, and 'Foo?' refers to a Foo or null
'Foo!' は nullではないFoo を参照し、'Foo?' は Foo または null を参照します。

Type variable types have nullness, too. Besides 'T!' and 'T?', there's also a "parametric" 'T*' that represents "whatever nullness is provided by the type argument".
型変数の型にも nullness があります。 「T!」のほかに および「T?」、「型引数によって提供される nullness が何であれ」を表す「パラメトリックな」「T*」もあります。

Kotlinの構文に雰囲気は似ているでしょうか。このあたりはおそらく大きく変更が入ることでしょう。あまり期待しすぎずに様子を見ましょう。


JEP 401 でもうひとつ触れられていることは non-atomic についてで、背景を説明するのが難しいのですが、端的に言えばマルチスレッド下で同時にValue型の値を作成するようなケースで、“Atomicity”(原子性/不可分性)を犠牲にしてパフォーマンスを出すオプションを用意しようというテーマです。本稿では深入りしません。

JEP 402: Enhanced Primitive Boxing

プリミティブ型を参照型のようにあつかう言語拡張です。プリミティブ型変数に対して、"."でメソッド呼び出しを行ったり、メソッド参照を用いたりすることができます。

int i = 12;
int iSize = i.SIZE;
double iAsDouble = i.doubleValue();
Supplier<String> iSupp = i::toString;

これにより、プリミティブ型と参照型の分断を軽減しようという試みのようです。仕組みとしては、ラッパー型のメソッドを呼び出すようです。

また、ジェネリクスの型変数に対してもプリミティブ型を用いれるようにします。この仕組みとしては、int を Integer! 型 にボクシングします。この ! は先に照会した Null-Restricted and Nullable Types (Preview) での null を許容しない型のことです。

この辺は個人的には好きなジャンルなのですが、深入りすると複雑になるので割愛します。

Universal Generics

端的に言えば、Value型と従来の参照型にまたがるユニバーサルなジェネリクスという内容です。

このへんのジャンルは私の好物なのですが、しかし具体的なところに踏み込むととにかく複雑かつマニアックになるので本稿では割愛します。

このJEPドラフトでは Collection<Point.ref> といったnull許容型の型変数を表す記法が暫定的に用いられています。ここでは Null-Restricted and Nullable Types (Preview) が参照されておらず、このあたりの統一的な方向性をどうするかといった部分はまだまだこれからなのでしょう。

null 許容型、非許容型が扱えるようになると型システムマニアの人達は狂喜するところなのですが。

総評

Project Valhalla の全体像を見てきました。端的なJEPではみえにくい全体像がいくらかでも掴めたようなら幸いです。
Java VM 側のプロトタイピングがひと段落したのか、ようやく上層のプログラミング言語Javaの部分の動きが活発になってきました。まだまだ先は長いですが、これからもProject Valhalla を注視していきたいと思います。