[ノンプロ研]オブジェクト指向でなぜつくるのか第3版 補章3 型推論と多相性

obnaze3プログラミング

どうも。つじけ(tsujikenzo)です。このシリーズでは2022年2月からスタートしました「ノンプロ研オブジェクト指向でなぜつくるのか第3版輪読会」の補章についてお送りします。今日は最終回です。

前回のおさらい

前回は、「パターンマッチングと再帰」をお送りしました。

[ノンプロ研]オブジェクト指向でなぜつくるのか第3版 補章2 パターンマッチングと再帰
どうも。つじけ(tsujikenzo)です。このシリーズでは2022年2月からスタートしました「ノンプロ研オブジェクト指向でなぜつくるのか第3版輪読会」についてお送りします。飛び入り参加しました、補章の資料で、今日は第2回目です。...

今回は、「型推論」をお届けします。

今日のアジェンダ

  • 型推論とは
  • 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つだと思います。

まとめ

以上で、「型推論」をお送りしました。

関数型プログラミング、少し身近に感じていただけましたでしょうか。

ノンプログラマーとしては、そこまで抽象度の高い部品を作ることはないかもしれませんので、必要以上に「関数型プログラミングを学んで、型推論と多相性を取り入れなければならない」と、焦る必要はないと思います。

ぜひ、こんなプログラミング手法もあるんだ、という風に楽しんでいきましょう。

参考資料

このシリーズの目次

タイトルとURLをコピーしました