Javaのための暗黙的に型定義される構造体 大久保 貴司 千葉 滋 東京工業大学 数理・計算科学専攻 テスト 文字の大きさ
静的型付け言語における構造体の利用 構造体(レコード型) 静的型付け言語における構造体 複数の異なる型のデータを まとめるために利用 まとめるために利用 静的型付け言語における構造体 定義が別途必要 クラス等 構造体は単純なデータ構造 値の出し入れさえできれば良い メソッドや関数は必要ない Java 人を表す構造体を 利用したい class Person{ String name; int age; } ・プログラミングにおいて複数のデータをまとめたい場合がよくあります。 ・そのような場合構造体(レコード型)がよく用いられます。 ・実際にはJavaなどのようにクラスベースオブジェクト指向言語では、クラスによって代用されるため、サポートされていないこと多い。 ・このような構造体を利用する場合、一般的に、まず構造体を定義して、それを用います。 例えば、図!! しかし Person p = new Person(); p.name = “hoge”; p.age = 20;
構造体の定義は省略したい 局所的にしか利用しない場合 タプルの利用 新たな定義なしに生成可能 構造体として扱う上での制限 任意の数のメンバをもつタプルは簡潔に記述できない 各メンバへのアクセスは名前でなく index等 不可変 なので構造体のための定義は省略しても良いのではないかと我々は考えます。 特にあるメソッドの一部など局所的に そこで、構造体と似たデータ構造としてタプルがあります。 タプルは異なるデータ構造を組として表します。 しかし構造体の変わりとして用いるにはいくらか制限があります。 言語によりますが、… val p = (“hoge”, 20) // p : Tuple2[String, Int] Scalaのタプル
動的型付け言語なら可能 オブジェクトの型を定義する必要がない 動的型付け言語だからできる記述法 オブジェクトのメンバ (プロパティ) は自由に追加可能 動的型付け言語だからできる記述法 静的型付け言語 にこのまま導入することは不可能 Java JavaScript class Person{ String name; int age; } 定義する必要なし ・一方、動的型付け言語では、そのような定義をわざわざする必要はありません。 ・なぜなら、型を明示的に宣言する必要がないからです。 ・例えば、JavaScriptでは、……プロパティを任意に追加できます。 ・従って、先ほどのプログラムは、クラス定義だけを省いたような記述で書くことができます。 ・しかし、これは動的型付け言語であるからできる記述法であり、静的型付けを維持したままJavaにこのような記述法を導入するのは難しい。 Person p = new Person(); p.name = “hoge”; p.age = 20; var p = {}; p.name = ”hoge”; p.age = 20;
提案: 暗黙的に型定義される構造体 動的型付け言語のような構文で構造体を利用可能 フィールド以外で利用可能 Javaで実現 ストラクトオブジェクトと var 型を導入 メンバを自由に追加可能 追加したメンバにはアクセス可能 フィールド以外で利用可能 そこで、本研究ではJavaにおける暗黙的に型定義される構造体を提案します。 本システムではストラクトオブジェクトとvar型を導入し、型を暗黙的に付けることで、先ほどのJavaScriptのコードのように、 クラス定義をせずに構造体を生成・利用が可能となっています。 これが本システムを用いたJavaで書いた構造体を利用するプログラムです。 ストラクトオブジェクトとはnew()で生成される構造体を表すオブジェクトであり、 Var型とはストラクトオブジェクトを参照する変数の宣言時の型です。 このvar型で宣言された変数pを用いて、自由にストラクトオブジェクトにメンバを追加でき、 追加したメンバの値は取り出すことができます。 Var型とはストラクトオブジェクトを参照する変数の宣言時の型であり、コンパイル時に暗黙的に型付けされます。 なので、JavaScriptのvar変数というよりは、C#のvar型に近く、それを構造体に適用したものです。 本システムを用いた Java var 型: ストラクトオブジェクトを参照する変数の宣言時の型 ストラクトオブジェクト( 構造体を表すオブジェクト)を生成 var p = new(); p.name = “hoge”; p.age = 20; int i = p.age;
暗黙的な型定義 structural type [Cardelli ‘88]+ 型推論 型はstructural type を用いて表現 型は内部のデータ構造によって区別される 型 = アクセス可能なメンバの集合 変数の型推論 構造体のメンバへの代入から適切な型を決定 変数が定義されているメソッド内をパース 本システムにおける構造体の型は、structural typeを用いて表現しています。 Structural typingとはその内部のデータ構造によって型が決定される型システムです。 本システムでは、型はアクセス可能なメンバの集合として表されます。 そして、アクセス可能なメンバというのはメンバの代入式から決定しています。 例えば、この例では 具体的には、まずストラクトオブジェクトの型が…のようにして決定されます。 変数 p の型 var p = new(); p.name = “hoge”; p.age = 20; int i = p.age; Struct{ String name; int age; } p.name = “hoge”; p.age = 10;
構造体のフィールドの型 構造体のフィールドへ異なる型のオブジェクトが代 入されている場合 フィールドの型 = 代入される各オブジェクトの型のアッ パーバウンドの型 class Shape{…} class Circle extends Shape{…} class Square extends Shape{…} var a = new(); a.obj = new Circle(); a. obj = new Square(); 先ほど説明した通り、構造体の型を決めるとき、メンバの代入式から、メンバの型を決定します。 あるメンバへの代入が1つだけなら、その代入しているオブジェクトの型をメンバの型と用いればよいのですが、 この場合、これらの代入している各オブジェクトのアッパーバウンドな型、 つまり、共通のスーパータイプの中で最もサブタイプである型がメンバの型となります。 この例では、 なぜなら、a.objはCircleもSquareも代入可能な変数です。従って、objの型はこれらのスーパータイプでなければなりません。 Object型とすれば、その中で 変数 a の型 Struct{ Shape obj; }
Subtyping new()によって初期化されていないvar 型の変数へ 既存のストラクトオブジェクトを代入 既存のストラクトオブジェクトを代入 必要に応じてスーパータイプを生成 共通のアクセス可能メンバのみメンバとして持つ var circle = new(); circle.color = “red”; circle.radius = 3; var square = new(); square.color = “blue”; square.size = 5; var shape; shape = circle; shape = square; struct{ String color; int radius; } circleの型 new()で 初期化 new()で 初期化 squareの型 struct { String color; int size; } 一方、var型の変数に他の変数が参照するストラクトオブジェクトを代入することも可能です。 図説明。 このような場合、 その変数の型はそれらの中で共通なアクセス可能なメンバになります。 具体的には、 このようにすることで、クラスの継承関係のような振る舞いが可能となる。 つまり、shapeの型がcircleとsquareの型のスーパータイプとして振る舞っている。 代入 shapeの型 Struct{ String color; }
メソッドへの受け渡し(1) ストラクトオブジェクトをメソッドに渡すことが可能 メソッドの仮引数の型にvar型を指定 仮引数の型 = 仮引数がメソッド内で満たすべき条件 を満たす型 簡単な記述のみ可能 (現在) 仮引数xの型 void f(var x){ String s = x.name; int age = x.age; : } Struct{ String name; int age; } var p = new(); p.name = “hoge”; p.age = 20; f(p); pがxのサブタイプ であれば引数としてOK
メソッドへの受け渡し(2) ストラクトオブジェクトをメソッドの返り値とすることが 可能 メソッドの返り値の型としてvar型を指定 var makePerson(){ var p = new(); p.name = “hoge”; p.age = 20; return p; } var person = makePerson(); makePerson, person共に以下の型がつけられる Struct{ String name; int age; } これを用いると、Javaなどでよく見られる、メソッドの返り値として、 複数のオブジェクトを返したい時に、わざわざそれ用のクラスを定義しなければいけないという問題を解決する事が可能となります。
p と同じ型のストラクトオブジェクトのみ格納可能 typeof の提供 型を取得するための演算子 本システムでは構造体の型の名前を明示的に指定で きないため必要 p と同じ型のストラクトオブジェクトのみ格納可能 var p ; Map<String, typeof(p)> map = new HashMap<String, typeof(p)>(); for(int i = 0; i < 5; i ++){ p = new(); p.name = “hoge” + i; p.age = 10; map.put(“p” + i, p); } 本システムでは、暗黙的に型を付けることで、記述を簡単にしているが、そのため構造体の型を明示的に型の名前を指定できない。 変数宣言はvarを用いて行われ、ストラクトオブジェクトの生成はnewで行われるため必要ないようにも思えるが、 ジェネリクスの型引数やinstanceofによる比較など、明示的に型の名前が必要になる場合が他にも色々あります。 そのため、構造体の型を取得するために、本システムではtypeof演算子を提供しています。 ー>図で説明 p の型 Struct{ String name; int age; }
実装 標準のバイトコードに変換 var 型 = Object 型 ストラクトオブジェクト 変数ごとにインタフェースを作成、必要に応じてキャスト インタフェースがアクセス可能なフィールドを表す ストラクトオブジェクト 上記のインタフェースを実装するクラスのインスタンス ストラクトオブジェクトを表すクラス class Var$$0 implements StringName{ String name; String getName(){return name;} void setName(String name){ this.name = name;} } 変数の型 本システムでは、 interface StringName{ String getName(); void setName(String name) }
実験 内部クラスによる構造体と本システムを比較 構造体を生成・操作するメソッドを100個定義し、実行 するプログラム コンパイル時間と実行時間を計測 内部クラスを用いた構造体 本システムを用いた構造体 run(){ class Person{…} Person p = new Person(); p.name = “hoge”; p.age = 5; : } run(){ var p = new(); p.name = “hoge”; p.age = 5; : }
実験結果 コンパイル時間 実行時間 型推論や暗黙的に行われるクラス・インタフェースの定義によってオー バーヘッドがかかっていると考えられる 既存のプログラムには影響を与えない 実行時間 実行時間には影響なく実現できた 本システム の利用 本システム約+40%
関連研究 Whiteoak [Gil ら ‘08] Scala他 Javaにおいて structural typeな型を宣言できる インスタンスは生成できない 型を明示的に定義する必要はある Scala他 型推論を行う言語 明示的に型を宣言する必要のない静的型付け言語 タプルを用いると、クラス定義せずに構造体を生成で きるが制限がある
まとめと今後の課題 まとめ 今後の課題 暗黙的に型が定義・付けられる構造体を実現 暗黙的に付けられた型を取得する typeof を提供 動的型付け言語の記述法を静的型付け言語である Javaで実現 暗黙的に付けられた型を取得する typeof を提供 今後の課題 var型の仮引数のメソッド内での利用の強化 形式化
実装 JastAddを用いてJastAddJ を拡張して実装 JastAddJ: JastAddを用いて 実装されたJavaコンパイラ Object p = new Ver$$0(); ((IntAge_StringName)p).setName(“hoge”); ((IntAge_StringName)p).setAge(20); int i = (IntAge_StringName)p).getAge(); class Ver$$0 implements IntAge_StringName{ int age; String name; String getName(){…} void setName(String name){…} } JastAddを用いてJastAddJ を拡張して実装 JastAddJ: JastAddを用いて 実装されたJavaコンパイラ var p = new(); p.name = “hoge”; p.age = 20; int i = p.age;
提案: 暗黙的に型付けされる構造体 型の定義を必要としない Java における構造体 局所的にのみ利用可能 ストラクトオブジェクト 構造体を表すオブジェクト new()で生成される var 型 ストラクトオブジェクトを参照する 変数の宣言時の型 コンパイル時に型が決定される 動的型付け言語のような構文で構造体を生成・利用可能 局所的にのみ利用可能 プログラム全体で用いる場合はクラス定義 本システムを用いた構造体 var p = new(); p.name = “hoge”; p.age = 20; int i = p.age; そこで、本研究ではJavaにおける暗黙的に型付けされる構造体を提案します。 本システムではストラクトオブジェクトとvar型を導入して、型を暗黙的に付けることで、先ほどのJavaScriptのコードのように、 クラス定義をせずに構造体を生成・利用が可能となっています。 ストラクトオブジェクトとは構造体を表すオブジェクトであり、 Var型とはストラクトオブジェクトを参照する変数の宣言時の型であり、コンパイル時に暗黙的に型付けされます。 なので、JavaScriptのvar変数というよりは、C#のvar型に近く、それを構造体に適用したものです。
構造体を表す型 structural type を用いて表現 ストラクトオブジェクトの型 var 型の変数の型 型 = アクセス可能なメンバの集合 ストラクトオブジェクトの型 代入された変数のメンバ代入式から決定 var 型の変数の型 変数に代入されたストラクトオブジェクトの型から決定 P の型 暗黙的に付けられる構造体の型は、structural typeを用いて表現しています。 Structural typeはフィールドとメソッドの集合で型を表現するものですが、 本システムではアクセス可能なメンバ、つまりフィールドの集合としてあらわしている。 そして、どのようにアクセス可能なメンバというのはメンバの代入式から決定しています。 具体的には、まずストラクトオブジェクトの型が…のようにして決定されます。 var p = new(); p.name = “hoge”; p.age = 20; int i = p.age; String name; int age; ストラクト オブジェクトの型 p.name = “hoge”; p.age = 10; String name; int age;
構造体(レコード型) 構造体 Javaでは直接的にはサポートされていない 複数の異なるデータ(型)をまとめるために利用 それぞれ構造体に対応するクラスを定義 Cの構造体 struct person{ char name[10]; int age; };