ジェネリクスとは
ジェネリクスとは、プログラミング言語において関数やメソッドの使用するデータ型を抽象化して、色々な型で処理を行えるようにする機能のことです。
ジェネリクスを使うのはどんなとき?
例えば引数の値を合計するという処理を書きたいとします。
しかし、静的型付け言語では一般的にはデータ型によって処理を分ける必要があります。
Int型(整数)を扱う場合に、抽象操作としてAddInt、string型(文字列)を扱う場合にはAddstringを準備する必要があります。
しかし、合計する処理は同じなので一つにして影響箇所を限定させつつ、処理を抽象化したいですよね。
それを解決するためにジェネリクスを使用します。
ジェネリクスで書くとどうなるのか
実際にジェネリクスで書いた場合をTypeScriptとGoでやってみようと思います。
TypeScriptの場合
ジェネリクスで書かない場合はこうなります。
function AddInt(a: number, b: number): number {
return a + b;
}
function AddString(a:string , b: string): string {
return a + b;
}
それをジェネリクスで書くとこうなります。
function Add<T extends number | string>(a: T, b: T): T {
if (typeof a === "number" && typeof b === "number") {
return (a + b) as T;
}
if (typeof a === "string" && typeof b === "string") {
return (a + b) as T;
}
throw new Error("Invalid type")
}
T extends
と書くことで指定した型に限定して扱うことができるようになります。
このTがジェネリクスとして機能し、Tを使うことで変数として型指定を行うことができます。
Goの場合
goの場合、ジェネリクスを使わなければこういうコードになります。
func AddInt(a: int, b: int ) int{
return a+b
}
func AddString(a: string, b:string) string{
return a+b
}
ジェネリクスで書くとこうなります。
func Add[T int | string](a: T,b :T) T {
return a + b
}
[]でジェネリクスの定義をしており、Tをintもしくはstringであるように定義しています。
ジェネリクスで使われている~
って何?
Goでのジェネリクスについて調べていると下のような書き方が出てきました。
func Add[T ~int | ~string](a, b T) T {
return a + b
}
~
とはunderlying Type
というものです。日本語で言うと基底型と呼ばれるものです。
~をつけると、指定した型をunderlying Type
にしてる型を使うことができます。
例えば下のような型も使えるようになります。
type MyInt int
type MyMyInt Myint
~int
としていたらそれをunderlying Type
としている型を使うことが可能になります。
なので、MyIntはintをunderlying Type
にしているので使うことができますし、MyMyIntも辿るとintがunderlying Type
になっているので使うことができます。
func main() {
var a MyMyInt = 1
var b MyMyInt = 2
fmt.Println(Add(a, b))
}
anyは使わない方がいい?
ジェネリクスで、型をanyとすることでどのような値でも引数や返り値として指定できるようになります。
function AddString(a:any , b: any): any {
return a + b;
}
func Add[T any](a, b any) any {
return a + b
}
しかし、これを使うと形チェックが行われず、ランタイムエラーを吐いてしまうため、静的型付けの良さを使うことができません。
可能な限り使わないようにしましょう。
まとめ
ジェネリクスは、型を柔軟に扱うことができ、メソッドの抽象化を行い再利用性をあげることができます。
便利なのでぜひ使ってみてください。