読者です 読者をやめる 読者になる 読者になる

kagamihogeの日記

kagamihogeの日記です。

JEP 218: Generics over Primitive Typesをテキトーに訳した

Java JEP テキトー翻訳

http://openjdk.java.net/jeps/218 をテキトーに訳した。

この辺に関してアンマリ知識が無いんで、訳は何時にもまして自信が無い。このエントリは、俺自身の英語とJavaの勉強の産物でしかないので、正確な情報はリンク先を参照願います。

JEP 218: Generics over Primitive Types

Owner    Brian Goetz
Created 2014/06/06 21:55
Updated 2014/10/24 15:56
Type    Feature
Status  Candidate
Component   specification / language
Scope   SE
Discussion  valhalla dash dev at openjdk dot java dot net
Effort  XL
Duration    XL
Priority    2
Reviewed by Maurizio Cimadamore
Release 10
Issue   8046267
Relates to  8058981: ClassDynamic support

Summary

プリミティブ型でのジェネリッククラスとインターフェースの特殊化*1をサポートするためにジェネリック型を拡張します。

Goals

ジェネリック型引数はObjectを拡張するよう強制されているため、プリミティブのインスタンスはボクシングを使用しない限り使用できず、パフォーマンスを低下させる一因となります。Javaに追加予定のvalue types(別のJEPが担当)が実現されれば、この制限はより一層重荷となります。我々はこの欠点を修正する提案を行います。提案内容は、ジェネリッククラスとインターフェースの特殊化をサポートすることで、これをプリミティブ型引数でインスタンス化される場合に使用します。

Non-Goals

完全なreified generics*2を提供することが目的ではありません。

Motivation

プリミティブをジェネリクスで扱うためにラッパークラス(Integerなど)を使用するのはコスト的に不利です。ボクシングは、多くのメモリ、多くの間接演算(indirection)、アロケーション、そして多くのGC、を要求します。ボクシングのオーバーヘッドを回避する試みは別の問題を引き起こし、それはIntStream, ToIntFunctionなどの擬似特殊化(pseudo-specialized)の蔓延です。8個のプリミティブ型だけがジェネリクスに反するのは、許容範囲内ではあるものの悩ましい存在です。そしてvalue typesの登場により、この制限は更に苦痛を伴うものになるでしょう。

ジェネリクスを持つ他の言語(例:C++, C#, Scala)は、プリミティブもしくは構造体での特殊化ジェネリクス(specialized generics over primitives or structs)のための各種サポートを提供しています。

Description

パラメトリックポリモーフィズム(Parametric polymorphism)はコードフットプリントと特殊化*3とのトレードオフを常に引き起こし、言語によって異なるトレードオフを選択しています。C++はテンプレートのインスタンス化ごとに特殊化クラスを生成し、一方、we have Java's current erased implementation which produces one class for all reference instantiations and no support for primitive instantiations.*4C#は参照と構造体の両方でジェネリクスを持ちます。バイトコードで両者を統合するアプローチで、インスタンス化された構造体ごとの特殊化表現と、すべての参照型ごとに、一つのネイティブコードを生成します。

他のトレードオフとしては特殊化のタイミングで、事前コンパイル(ahead-of-time)(Scalaなど)やオンデマンドコンパイル(on-demand)(C#など)やディレイ特殊化(delayed specialization)があり、コンパイラが生成する共有アーティファクトジェネリック(C#のようにすべての場合に特殊化を要求する)かどうか、特定のインスタンス化パターンに限定するかどうか、があります。

Example: a simple Box class

以下のクラスをT=intで特殊化したいとします。

class Box<T> {
    private final T t;

    public Box(T t) { this.t = t; }

    public T get() { return t; }
}

コンパイルすると現在のバージョンでは以下のバイトコードが生成されます。

class Box extends java.lang.Object{
private final java.lang.Object t;

public Box(java.lang.Object);
  Code:
   0:    aload_0
   1:    invokespecial    #1; //Method java/lang/Object."<init>":()V
   4:    aload_0
   5:    aload_1
   6:    putfield    #2; //Field t:Ljava/lang/Object;
   9:    return

public java.lang.Object get();
  Code:
   0:    aload_0
   1:    getfield    #2; //Field t:Ljava/lang/Object;
   4:    areturn
}

このバイトコードにおけるObjectは型変数を消去していることになります。もしT=intでこのクラスを特殊化したら、get()シグネチャの戻り値はintを期待することになります。同様に、バイトコードa*のいくつかはバイトコードi*になります*5

バイトコードで必要なジェネリクスの表現には様々なアプローチがあります。(.NETのように)バイトコードレベルで完全なジェネリクスを保持するものから、型やバイトコードとソースファイルの型を直接関連付けるかどうかを示すためにバイトコードと型に少量のタグを不要するものや、型変数を除去するものまで。

実行時のオンデマンド特殊化を現実的なものにするには、特殊化は出来る限りシンプルかつ機械的であるべきです。我々は、データフロー解析を追加したり、既存の検証以上の実行時型検査は、望ましくないと考えています。同様に、特殊化したものは既存の検証ルールで検証可能であるべきです。

Open questions

本機能が提案可能になる前に、答えるべき多数の質問が寄せられました。

  • サブタイプ。特殊化ジェネリクス(例:List<int>)とそれに対応する型(List)との型の相互変換はどうなるのか?
  • 型の表現。メソッド引数・フィールドの型・スーパータイプなどで使われる場合、特殊化された型はバイトコードでどのように表現するのか?
  • メカニクスジェネリッククラスはどのように特殊化するのか? その都度行うのか? プラットフォームのコンポーネントに依存するのか?*6
  • リフレクション。特殊化クラスをリフレクションで参照する場合、どのように振る舞うのか?
  • オプトインもしくはオプトアウト。ジェネリックな型変数Tコンパイル*7Objectの拡張と見なされます。参照およびプリミティブ/value typesの両方でジェネリクスを使用したい場合にはどうすればよいのか?*8
  • ジェネリックメソッド。クラスが特殊化可能だとすると、ジェネリックメソッドでも特殊化は可能でなければならない。既存のクラスに任意の多数の新規メソッドを追加可能なのか?(理想的には、vtablesのレイアウトを変更せずに)
  • 配列。ArrayListなどのクラスは頻繁にObjectからTにキャストされますが、もしTintだとすると、これは解決が難しい問題です。ArrayListは特殊化可能になるので、我々は実行可能なセマンティクスをnew T[]に割り当てる予定です。
  • 参照型とプリミティブのオーバーロード(Reference-primitive overloadings). 現在のバージョンでは妥当なオーバーロードが特殊化では解決の難しい問題になりえます。たとえば、remove(int)remove(T)をオーバーライドしているListライクのクラスはTが参照型に制限されている場合は解決可能ですが、Tintも許すとなると解決の難しい問題になります。
  • Null. Nullはいずれの参照型の値でも妥当で、何も無い("nothing is there")ことの表現に使われることがあります。しかし、プリミティブとvalue typeにはnullに相当するものがありません。指定されたキーがmapに見つからない場合にnullを返すよう定義されているMap.getなどはどうするのか、という課題が存在します。
  • 手作業での移行(Hand-written replacements). ジェネリックなクラスを特殊化したものへと機械的に変換することは容易ですが、多くの制限があります。我々はユーザ制御下での高度なサポートを望んでいます。例えば、ArrayList<boolean>の最適化バージョンはBitSetを使用して書くことが出来ます。このバージョンは特殊化が生成するboolean[]の代わりに裏側ではBitSetを使用します。
  • 改良(Refinements). 特定の型のインスタンスを使用するメソッドをオーバーライドする(もしくは新しいメソッドを作る)ことで、機械的に変換される特殊化に対する、ユーザ制御下での代替手段になると思われます。たとえば、sum()を持つList<int>や、特定の型のインスタンスを使用する既存メソッドの最適化バージョンを作成できます。
  • 不完全なジェネリクス(Incomplete generification). ジェネリクスが不完全となっているクラスが存在します。たとえば、Collection.removeの引数型はObjectであってTではありません*9。こうしたクラスでも適切な特殊化を許容するメカニズムが必要となります。
  • 型推論(ype inference). <Z> m(Z a, Z b)というジェネリックメソッドm(1,2)として実行される場合、Zの推論はintIntegerのどちらになるべきか?int型推論することは、型システムに関する問題提起(このケースでLUBとGLBはどのような振る舞いをするのか?)となり、また、ソース互換性の問題提起にもなります。

See also

State of the Specialization

*1:specialization of generic classes and interfaces over primitive typesが原文

*2:http://stackoverflow.com/questions/879855/what-are-reified-generics-how-do-they-solve-the-type-erasure-problem-and-why-ca とか http://rgomes-info.blogspot.co.uk/2013/12/using-typetokens-to-retrieve-generic.html とかを読んだ感じでは、実行時にも具体的なジェネリクスの型が見える、て感じ。現行Javaのtype erasureでは実行時にぜんぶObjectになるので、実行時にジェネリクスの型が欲しかったら、何か別のテクニックで渡してやるしかない。reifiedは「具体的な~」という意味なので、型が無いことのカウンターとしてreifiedという単語を使ってる(と思う) そんで、このJEPはプリミティブでは部分的にはreifiedになるけど、完全にreifiedするわけではないよ、と但し書きしている

*3:specificityが原文

*4:上手く訳せず。文脈的にtype erasureのことを言っていることだけはわかるのだが……

*5:http://ja.wikipedia.org/wiki/Java%E3%83%90%E3%82%A4%E3%83%88%E3%82%B3%E3%83%BC%E3%83%89 「多くの命令は、動作するオペランドの型を示す接頭辞や接尾辞を持つ。」で「a reference, i integer」とある。reference用のバイトコードをinteger用のバイトコードに変更する、ってぐらいの意味ですかね

*6:By what platform component?が原文。プラットフォームに依存するのか?って感じ?

*7:in the absence of a boundが原文。直訳すれば「境界の外側では~」ぐらいだけど、文脈的にtype erase後の話だと思うのでこうした

*8:How would we denote that we wish to generify over both reference and primitive/value types?が原文。意味的には『type erasureでぜんぶObjectになるので、あるコードで参照とプリミティブ両方を使うにはどうするのか?』 的な感じ?

*9:https://docs.oracle.com/javase/jp/8/api/java/util/Collection.html#remove-java.lang.Object-