型推論
出典: フリー百科事典『ウィキペディア(Wikipedia)』 (2022/06/22 01:55 UTC 版)
型推論のバリエーション
型推論により自動的に型を決定する機構は、変数宣言の際の暗黙的な型指定以外にも存在する。関数型言語ではほとんどの場面で型推論がサポートされるが、従来の手続き型言語やオブジェクト指向言語でのサポートは言語および各言語の規格バージョンによってまちまちである。
変数宣言時の型推論
C#はバージョン3.0にて、var
キーワードを用いたローカル変数の宣言時型推論を導入した。制約のひとつとして、宣言時初期化を伴う必要がある。for文、foreach文、using文のスコープ変数にも利用できる。宣言時初期化文の右辺がメソッドグループや匿名関数(ラムダ式および匿名メソッド)の場合には適用できない[2]。
// 型推論を用いない書き方。
string s1 = "文字列";
System.Console.WriteLine(s1.GetType()); // System.String
// 型推論を用いた書き方。
var s2 = "文字列";
System.Console.WriteLine(s2.GetType()); // System.String
var now = System.DateTime.Now;
System.Console.WriteLine(now.GetType()); // System.DateTime
now = s2; // コンパイルエラー。
var action1 = () => {}; // コンパイルエラー。
var action2 = delegate() {}; // コンパイルエラー。
var action3 = System.GC.Collect; // コンパイルエラー。
var action4 = new System.Action(() => {});
System.Action action5 = () => {};
var dict = new System.Collections.Generic.SortedDictionary<string, int> { {"Bravo", 0}, {"Alpha", 1}, {"Charlie", 2} };
foreach (var entry in dict) {
//System.Diagnostics.Debug.Assert(entry is System.Collections.Generic.KeyValuePair<string, int>);
System.Console.WriteLine("Key={0}, Value={1}", entry.Key, entry.Value);
}
この書き方はJavaScriptなど動的型付けの言語に非常によく似ているが、しかしながらすべての型はコンパイル時に定められる。また、バリアント型とは異なり、実行時に再代入によって変数の中身の型が変わるようなことはない。
ラムダ式の仮引数の型を省略した場合も型推論が働く。戻り値の型は常に型推論によって決定される。
// ラムダ仮引数の型推論を用いない書き方。
System.Func<double, double> func1 = (double x) => x * x;
// ラムダ仮引数の型推論を用いた書き方。
System.Func<double, double> func2 = (x) => x * x;
Javaはバージョン8にてラムダ式を導入したが、C#同様に仮引数の型を省略すると型推論が働く。バージョン10にて、予約型名var
によるローカル変数の宣言時型推論を導入した。バージョン11にて、ラムダ式の仮引数の型推論にもvar
を使えるようになった。
C++はC++11規格にて、キーワードauto
およびdecltype
による一部の変数の宣言時型推論を導入した[3]。適用可能範囲はC#やJavaよりも広い。後継規格のC++14以降ではさらに適用可能範囲が広がっている。
namespace {
auto g_variable = 0.0; // double
struct MyType {
static const auto s_variable = 0L; // long
};
}
int main() {
auto n = 0; // int
decltype(n)* p1 = &n; // int*
decltype(&n) p2 = &n; // int*
decltype(n)& r1 = n; // int&
decltype((n)) r2 = n; // int&
auto f = []() {}; // コンパイラが生成する関数オブジェクト(クロージャ)型。
}
戻り値の型推論
C++はC++11規格にて、キーワードauto
およびdecltype
による戻り値の型推論を導入した[4]。後継規格のC++14ではdecltype(auto)
による簡略表現もサポートする。
#include <iostream>
template<typename TFunc, typename TArg> auto invokeFunc(const TFunc& f, const TArg& a) -> decltype(f(a)) {
return f(a);
}
int main() {
std::cout << invokeFunc([](double x) { return x * x; }, 1.4142) << std::endl;
}
総称型の型推論
C++では、関数テンプレートに対してテンプレート実引数(具体的な型名)を明示的に与えて型を決定することもできるが、曖昧さがない場合に限り、関数呼び出しの実引数に応じて型を推論させることもできる。
#include <iostream>
#include <cmath>
template<typename T> T getVectorLength(T x, T y, T z) {
return std::sqrt(x * x + y * y + z * z); // std::sqrt() には double あるいは float を受け取るオーバーロードが存在する。
}
int main() {
const double len1 = getVectorLength<double>(1, 2, 3); // double getVectorLength(double, double, double)
const float len2 = getVectorLength(1.0f, 2.0f, 3.0f); // float getVectorLength(float, float, float)
std::cout << len1 << std::endl;
std::cout << len2 << std::endl;
}
テンプレート仮引数T
を持つ関数テンプレートにおける仮引数の型宣言が、参照T&
あるいはポインタT*
であったり、ユニバーサル参照T&&
であったりする場合は、推論の結果として定まる型が異なる場合もある。
C++17ではクラステンプレートのテンプレート引数を推論することもできるようになった[5]。
template<typename T> struct Vector3 {
T x, y, z;
Vector3(T ax, T ay, T az) : x(ax), y(ay), z(az) {}
};
int main() {
Vector3<double> v1(1.0, 2.0, 3.0); // C++03 以前でも利用可能な、従来のコンストラクタ呼び出しによる実体化。
Vector3<double> v2 { 1.0, 2.0, 3.0 }; // C++11 以降の uniform initialization を使用した実体化。
Vector3 v3(1.0, 2.0, 3.0); // C++17 以降でのみ有効。Vector3<double> に推論される。
Vector3 v4 { 1.0, 2.0, 3.0 }; // 同上。
}
Javaはバージョン5.0以降にてメソッドスコープの型変数を推論する機能を持つ。
// 型推論を用いない書き方。
List<String> list1 = Collections.<String>emptyList();
// 型変数へのバインドに型推論を用いた書き方。
List<String> list2 = Collections.emptyList();
その他、Java 7 からは型変数を持つクラスをnewする場合にバインドすべき型を推論するダイヤモンド演算子という機能を持つ。
// 型推論を用いない書き方。
List<String> list1 = new ArrayList<String>();
// ダイヤモンド演算子による型推論。
List<String> list2 = new ArrayList<>();
無名関数の型推論の例
無名関数の型推論においては、複雑な状況が発生する。
C#の例を以下に示す。
// 複数のデリゲート型を定義
delegate void TwoStringAction(string left, string right);
delegate void OneParamAction(object o);
delegate void TwoParamAction(object o, EventArgs e);
delegate void TwoIntegerAction(int x, int y);
// メソッドのオーバーロードを用意する。有効化するオーバーロードの種類により、型推論の可否が変化する。
static void SomeMethod(TwoStringAction action) { /* Pattern 1 */ }
//static void SomeMethod(OneParamAction action) { /* Pattern 2 */ }
//static void SomeMethod(TwoParamAction action) { /* Pattern 3 */ }
//static void SomeMethod(TwoIntegerAction action) { /* Pattern 4 */ }
static void Main() {
// メソッドのオーバーロードがPattern 1のみの場合、全ての文が有効(型推論可能)である。
SomeMethod((o, e) => { /*No-op*/ }); /* 1行目 */
SomeMethod((o, e) => { o = o + e; }); /* 2行目 */
SomeMethod((o, e) => { o = "" + o + e; }); /* 3行目 */
SomeMethod(delegate { /*No-op*/ }); /* 4行目 */
}
SomeMethod(OneParamAction action)
の行を有効にすると、Mainメソッドの4行目の型推論が効かなくなる。- 4行目の匿名関数は引数リストを省略できるため、1引数、2引数のどちらのデリゲート型か曖昧になる。
SomeMethod(TwoParamAction action)
の行を有効にすると、Mainメソッドの2行目以外の型推論が効かなくなる。- 1行目のラムダは、2引数で値を返さない複数のデリゲート型がある場合、曖昧となる。
- 3行目のラムダは、代入式の左辺にある
o
はstring
を格納できる型でなければならないが、
一方で右辺のo
とe
は暗黙にToString()
が呼び出されるため、いずれの型であってもよく、TwoStringAction
TwoParamAction
の間で曖昧となる。 - 2行目のラムダは、
o
とe
の間に適切なoperator+
が定義されなければならず、TwoStringAction
型と推論される。
SomeMethod(TwoIntegerAction action)
の行を有効にすると、Mainメソッドの3行目以外の型推論が効かなくなる。- 2行目のラムダは、
o
とe
はstring
かint
のいずれでもoperator+
が実行できるため、TwoStringAction
TwoIntegerAction
の間で曖昧となる。 - 3行目のラムダは、代入式の左辺にある
o
はstring
を格納できる型でなければならないため、TwoStringAction
型と推論される。
- 2行目のラムダは、
デリゲート型を引数に取るメソッドを複数用意する場合、オーバーロードではなく別名のメソッドとすることで、この複雑性は回避できる。
- ^ C言語は関数の戻り値の型を省略した場合、
int
を返すと仮定する仕様になっているが、これを型推論とは呼ばない。 - ^ 暗黙的に型指定されるローカル変数 - C# プログラミング ガイド | Microsoft Docs
- ^ auto - cpprefjp C++日本語リファレンス
- ^ decltype - cpprefjp C++日本語リファレンス
- ^ クラステンプレートのテンプレート引数推論 - cpprefjp C++日本語リファレンス
- ^ a b Type Inference brings JS improvements to Firefox Beta Brian Hackett, 2011年11月10日(2011年12月24日閲覧)。
型推論により Firefox Beta の JavaScript が高速化しました(上の記事の和訳)、2011年12月24日閲覧。 - ^ 一色政彦 (2011年4月22日). “Internet Explorer 9正式版レビュー”. @IT. 2011年12月24日閲覧。
- ^ “Mozilla、「Firefox 9」の正式版をリリース 「型推論」技術で45%高速に”. ITmedia (2011年12月21日). 2011年12月24日閲覧。
- 1 型推論とは
- 2 型推論の概要
- 3 具体例による説明
- 4 型推論のバリエーション
- 5 動的型言語における型推論
型推論と同じ種類の言葉
- 型推論のページへのリンク