どうも。つじけ(tsujikenzo)です。このシリーズでは2022年2月からスタートしました「ノンプロ研オブジェクト指向でなぜつくるのか第3版輪読会」の補章についてお送りします。今日は最終回です。
前回のおさらい
前回は、「パターンマッチングと再帰」をお送りしました。
今回は、「型推論」をお届けします。
今日のアジェンダ
- 型推論とは
- 2つのコレクション
- ジェネリクスと多相性
型推論とは
静的型付け言語は、コンパイル段階でエラーを検出するので、型を強制する安全性の高いプログラミングができます。
いっぽう、動的型付け言語は、実行時に型が決まるため、型にしばられない柔軟性をもったプログラミングができます。
この2つは、トレードオフ(利害の相反)の関係にあるように思われます。
そんなお悩みを解決する、現代の静的型付け言語では、「ソースコードを書くときは型を指定しなくてもいいけど、コンパイル時に型をチェックできるようにしよう」という、「型推論」 が導入されています。
Java SE10で実装された、「var宣言による型推論」を見てみましょう。
Javaでは、varによる変数宣言をおこなうことで、演算結果を代入するときに推測される型を、コンパイル時にチェックできます。
// int型を指定した変数宣言
int i = 10;
// str型を指定した変数宣言
String str = "hoge";
// 演算結果を代入する、具体的な型を指定した変数宣言
String a = i + str;
int b = i + str; // コンパイルエラー
// varによる変数宣言
var a = i + str;
var b = i + 10;
System.out.println(a); // 10hoge
System.out.println(b); // 20
variableとは、「変わりやすい、変化しやすい」という意味です。
また、このように、メソッドの戻り値から、型推論を行うこともできます。
var x = sample();
System.out.println(x);
//ラムダ式では使えない
var lamda = () -> {}; //コンパイルエラー
型推論により、具体的な型を指定せずに、抽象的に型をとらえる、というメリットが得られます。(後述します)
2つのコレクション
Javaでは、なんらかの集合のことを 「コレクション(Collection)」 と呼びます。
1つは配列です。
- 同じ型、もしくは互換性のある型しか扱えない
- 扱える要素数を最初に決めなくてはいけない
// 整数型の要素を格納する配列
int[] values = { 1, 2, 3 };
for (int value : values) {
System.out.println(value); // 1,2,3
}
// 配列には、互換性のない型を格納できない
// String[] array2 = { "hoge", "hoge", 3}; //コンパイルエラー
しかし、「要素の型を限定したり、要素数を増減できないのは不便だから、もっと動的にコレクションを操作できたらいいのにね。」というニーズに応えたのが、ArrayListクラスです。
ArrayList list = new ArrayList();
list.add("hoge");
list.add(10);
list.add(true);
for (Object obj : list) {
System.out.println(obj); // hoge, 10, true
}
ArrayListは、配列のように値を番号で管理するため、追加した順に値が並びます。
このように値が順番に並んだ構造を 「リスト構造」 と呼びます。
JavaScriptの配列が、さまざまなデータ型を格納(厳密には参照)できるには、動的型付け言語の大きな特徴です。
const values = [1,"hoge", true];
for (const value of values){
console.log(value); //1,"hoge", true
}
JavaScriptでは、「想定していないデータ型が配列に紛れていないか」を実行前にチェックすることを、防御的プログラミングと言います。(詳しくは別のブログで)
ジェネリクスと多相性
ArrayListクラスは、どんな型でも取り扱いができましたが、ジェネリクスという機能をつかうと、あらかじめ型を指定することもできます。
ジェネリクスは、指定する型(型パラメータと呼びます)を、ArrayListの後の<>ダイヤモンド演算子内で指定します。
以下のコードでは、String型以外の型を、ArrayListに追加しようとすると、コンパイルエラーが発生します。
ArrayList<String> list = new ArrayList<>();
list.add("hoge");
list.add(10); //コンパイルエラー
list.add(true); //コンパイルエラー
for (String str : list) {
System.out.println(str);
}
この、総称(ジェネリック)化された型とみなすことができるデータ型(任意の型の要素を持てるリスト)のことを「多相なデータ型」と呼びます。
また、異なる型の値に対して評価、適用可能な関数のことを「多相な関数」と呼びます。
型推論と多相性
型推論には、
- 関数のばあい、関数の戻り値に対しても型推論を行える。
- データ型のばあい、コレクションに格納される型は動的に推測する(制限もできる)。
という機能がありました。
つまり、型推論と多相性を組み合わせると、静的型付け方式のメリットである、
- コンパイル時のエラー判定を可能にしながら、
- 汎用性の高い(不特定多数の型に適用できる)関数を記述できる
を実現できます。
関数型プログラミングは、型推論と多相性の仕組みを提供しています。
ライブラリやフレームワークのような、抽象度の高い部品を作成するときに、関数型プログラミングが選ばれている理由の1つだと思います。
まとめ
以上で、「型推論」をお送りしました。
関数型プログラミング、少し身近に感じていただけましたでしょうか。
ノンプログラマーとしては、そこまで抽象度の高い部品を作ることはないかもしれませんので、必要以上に「関数型プログラミングを学んで、型推論と多相性を取り入れなければならない」と、焦る必要はないと思います。
ぜひ、こんなプログラミング手法もあるんだ、という風に楽しんでいきましょう。
参考資料
- オブジェクト指向でなぜつくるのか 第3版 平澤 章著 日経BP
- 徹底攻略Java SE 11 Silver問題集[1Z0-815]対応 志賀 澄人著 インプレス
- IT用語辞典 e-Words ポリモーフィズム 【polymorphism】
- 【やさしくない!? Java】ジェネリクスの話1 OCJ-P Gold向け
- ポリモーフィズム Wikipedia