型推論
型推論(かたすいろん、英: type inference)とはプログラミング言語の機能の1つで、静的な型付けを持つ言語において、変数や関数シグネチャの型を明示的に宣言しなくても、変数宣言における初期化のための初期値や、関数呼び出しにおける実引数などといった、周辺情報および文脈などから自動的に(暗黙的に)各々の型を決定する機構のこと。言語によってはtype deductionと呼ばれることもある。 推論に失敗するとその時点でエラーを報告できるため、少なくとも誤った型を用いることによるバグは回避できる。また、アルゴリズムの記述に集中できるのでプログラムの抽象度が上がるというメリットもある。型名が長大な場合に、型推論による省略によってコード全体の見通しをよくすることにもつながるが、一方で統合開発環境による支援(コードエディター上のツールチップなど)が得られない環境では、一見して型が分からないことでコードレビューがしにくくなるというデメリットもある。 代表的な型推論アルゴリズムとして、Hindley/Milner 型推論アルゴリズムがある。各々著名なコンピュータ科学者の名前からつけられた名前であるが、Hindley は論理学者として型推論システムを先に開発した。 型推論を持つ言語としてはHaskell、ML、Vala、OCaml、F#、C#、Java、Scala、C++、D言語、Concurrent Clean、Swiftなどがある。静的型付け関数型プログラミング言語のほとんどがなんらかの型推論の機能を持っている。登場当初は型推論を持っていなかった言語であっても、関数型言語に影響を受けた拡張や改訂により型推論の機能を持つようになった言語も多い。 ただし型推論と関数の多重定義(オーバーロード)は相性が悪く、オーバーロードをサポートする言語では型推論による恩恵が十分に受けられない(型推論ではシグネチャを一意に決めることができない)ケースがある。 具体例による説明この節では型推論の構文解析理論には踏み込んでいない。 ほとんどの言語においては、関数の仮引数および戻り値、演算子のオペランドおよび結果、変数、そしてそれらから成る式は、各々が保持するデータの種類を表す型を持つ。構文上で明らかな名前を持つ型による区別をしない言語であっても、内部的にはなんらかの型を持っていて区別しているケースが多い。実行時に型が決まる言語を動的型付けの言語という。一方、コンパイル時に型が決まる言語を静的型付けの言語という。静的型付けの言語において、関数の仮引数および戻り値の型や変数の型は、通常は明示的に記述する必要がある。例えば、次はC言語の例である[1]。 int addone(int x) {
int result;
result = x + 1;
return result;
}
関数定義の最初の行 上記の例にほぼ1対1で対応するコードを、F#を使って記述すると下記のようになる。 let addone (x : int) : int =
let result : int = x + 1
result
しかしF#は型推論の機能を持っているため、次のように書くこともできる。 let addone x =
let result = x + 1
result
このF#の例において、
という仕様であり、右オペランドには整数リテラル let y1 = addone 3
let y2 = addone 3.0 // double 型の値を渡すと、型の不一致によりコンパイルエラー。
let y3 = addone 3y // sbyte 型の値を渡すと、型の不一致によりコンパイルエラー。
なお、型推論はあくまで暗黙の型付けがなされるにすぎない。型推論によりコンパイル時に確定した型は不変である。 型推論のバリエーション型推論により自動的に型を決定する機構は、変数宣言の際の暗黙的な型指定以外にも存在する。関数型言語ではほとんどの場面で型推論がサポートされるが、従来の手続き型言語やオブジェクト指向言語でのサポートは言語および各言語の規格バージョンによってまちまちである。 変数宣言時の型推論C#はバージョン3.0にて、 // 型推論を用いない書き方。
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にて、予約型名 C++はC++11規格にて、キーワード 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規格にて、キーワード #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;
}
テンプレート仮引数 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行目 */
}
デリゲート型を引数に取るメソッドを複数用意する場合、オーバーロードではなく別名のメソッドとすることで、この複雑性は回避できる。 動的型言語における型推論動的に型付けを行う言語の場合、文法的には型付けが行われず、あらゆる型の可能性を考慮して処理を進める必要があるため、処理が遅くなる原因となる[6]。JITコンパイラによって高速化を図る場合、型推論によりあるデータを「特定の型」として扱うことが可能であれば、その型に合わせた処理だけをすることで高速化が行える[6]。 JavaScript では、Webブラウザの分野では高速化が特に求められている[7]ため、2011年12月20日にリリースされたFirefox 9から高速化のため型推論技術を採用している[8]。 Groovy 2.0 ではコンパイル時型検査 脚注
|