Zen言語は型を明示的に記述するコンパイル型言語です。そのため、様々な場所に型を記述しなければなりません。特に、関数の引数や戻り値の型については、型を省略することができません。次のようなu32
の値を2倍にする関数があるとします。
fn double(x: u32) u32 {
return x * 2;
}
この関数は、u32
の引数と戻り値にのみ、利用することができます。では、f64
の値を2倍したい場合はどうすれば良いでしょうか?新しく次のような関数を用意しなければならないのでしょうか?
fn doubleF64(a: f64) f64 {
return a * 2;
}
もちろん、このようなことをする必要はありません。Zenでは特定の型に依存しない関数を記述することが可能です。そのような特定の型に依存しないようなプログラミング手法をジェネリクスと呼びます。
ここでは、Zenのジェネリクスについて学びます。
特定の型に依存しない関数をジェネリック関数と呼びます。Zenでジェネリック関数を作る方法は、大きく分けて2つあります。anytypeパラメータを使う方法と、comptime typeを渡す方法です。
まず、anytypeパラメータを使う方法を説明します。次のコードでは、引数a
の型はanytype
になっています。
examples/ch06-polymorphism/src/generics.zen:4:6
fn doubleByVarParam(a: anytype) @TypeOf(a) {
return a * 2;
}
このように書くと、引数a
の型は、コンパイル時に推論されます。また、戻り値の型を@TypeOf(a)
とすることで、戻り値の型はa
と同じ型になります。
上のコードの使い方を見てみましょう。ここでのポイントは、doubleByVarParam
を2回呼び出していますが、それぞれの呼び出しで戻り値の型が異なっていることです。関数からの戻り値型が、引数の型と等しくなっています。
examples/ch06-polymorphism/src/generics.zen:8:16
test "use generic doule by parameter's type" {
const resultU32 = doubleByVarParam(@to(u32, 1));
ok(@TypeOf(resultU32) == u32);
ok(resultU32 == 2);
const resultF64 = doubleByVarParam(@to(f64, 1));
ok(@TypeOf(resultF64) == f64);
ok(resultF64 == 2.0);
}
次に、comptime typeを渡す方法での実装を紹介します。この方法では、関数の引数として型を渡します。次のコードの第一引数 (comptime T: type
) は、型を渡しています。type
は何らかの型であることを意味する型です。
この宣言以降、T
は型名として利用可能です。型名は必ずしもT
である必要はありませんが、型 (Type) の頭文字であるT
を使うことが多いです。
examples/ch06-polymorphism/src/generics.zen:18:20
fn doubleByComptimeType(comptime T: type, a: T) T {
return a * 2;
}
この関数の使い方をお見せします。u32
とf64
として使用する場合、次のようになります。doubleByComptimeType
関数を2回呼び出していますが、それぞれの戻り値型は異なっています。
examples/ch06-polymorphism/src/generics.zen:22:32
test "use generic double" {
// u32
const resultU32 = doubleByComptimeType(u32, 1);
ok(@TypeOf(resultU32) == u32);
ok(resultU32 == 2);
// f64
const resultF64 = doubleByComptimeType(f64, 1);
ok(@TypeOf(resultF64) == f64);
ok(resultF64 == 2.0);
}
ここまでで2つのジェネリクス実現方法を説明しました。では、なぜ2つの方法があるのでしょうか?上の例では、anytypeパラメータを使う方法のほうが、引数が1つで済んで良いように思えます。
その答えは、型を推論できない場合にcomptime type
を使う、というものです。一例として、任意の整数型から別の整数型に変換する組込み関数@intCast
を見てみましょう。
@intCast
の関数シグネチャは次の通りです。第一引数にターゲットとする整数型、第二引数に任意の整数型 (関数シグネチャとしては任意の型ですが、整数型以外はコンパイルエラーになります) を受け取ります。
@intCast(comptime DestType: type, int: anytype) DestType
任意の整数型を受け取るために第二引数はanytypeパラメータ
を使用していますが、第二引数の型からターゲットの整数型を推論することはできません。例えば、u32
からu64
に変換したい場合、u64
に変換したいことがu32
の型情報からだけでは、コンパイラには知りようがありません。そこでコンパイラにターゲットとする整数型を教えてあげるために、comptime type
を使用します。
このように関数の引数型からだけでは、型が決定できないような場合、comptime type
を使ったジェネリクスを使用します。
関数だけではなく、ジェネリックな構造体を定義することができます。ジェネリックな構造体を定義する際は、無名構造体の型を戻り値とする関数を定義します。例えば、任意の型を格納できるスタックは、次のように定義可能です。
examples/ch06-polymorphism/src/generics.zen:48:72
fn Stack(comptime T : type) type {
return struct {
items: []mut T,
top: usize,
// ...
};
}
関数の戻り値型がtype
になっています。これは、関数の戻り値が型であることを意味しています (これまで見てきた関数はある型の値を返していました) 。Stack
関数の戻り値型は、保持するデータ型 (T
) によって型が変わります。そのため具体的な型名を戻り値型として記述することができません。
そこで具体的な型名の代わりに、何らかの型を返すことを意味するtype
を戻り値型として指定し、実際の型は関数呼び出し時点で決定します。
Stack
関数から返している構造体には名前がついていません。このような構造体を無名構造体と呼びます。無名構造体は、今回の例のように具体的な型名をプログラマが命名できない場合に便利です。
では、Stack
の利用例を見てみましょう。Stack
関数の戻り値は、構造体の型です。そのため、そのまま構造体の初期化を続けて記述することができます。
examples/ch06-polymorphism/src/generics.zen:34:48
test "generic stack test" {
// `Stack`の返す無名構造体インスタンスを作成する
var items_u32 = [_]u32{0} ** 10;
var stack_u32 = Stack(u32){
.items = &mut items_u32,
.top = 0,
};
// `Stack`の返す無名構造体インスタンスを作成する
var items_f64 = [_]f64{0.0} ** 10;
var stack_f64 = Stack(f64){
.items = &mut items_f64,
.top = 0,
};
}
もちろん、一度、Stack
関数が返す構造体型に名前を与えることも可能です。一般的には、一度命名する方が好ましいと言えるでしょう。
const StackU32 = Stack(u32);
ジェネリック構造体に関数およびメソッドを持たせることも可能です。ジェネリック構造体は、無名構造体を扱うため、構造体名がわかりません。そこで、@This
組込み関数を利用して、構造体型名をSelf
とするのが慣習です。
examples/ch06-polymorphism/src/generics.zen:50:74
fn Stack(comptime T: type) type {
return struct {
const Self = @This();
items: []mut T,
top: usize,
fn init(buf: []mut T) Self {
return Self{
.items = buf,
.top = 0,
};
}
fn push(self: *mut Self, item: T) void {
self.items[self.top] = item;
self.top += 1;
}
fn pop(self: *mut Self) T {
self.top -= 1;
return self.items[self.top];
}
};
}
Self
を型名として使用していることと、型T
が随所に現れる以外は、通常の構造体と変わりありません。
☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2020 connectFree Corporation. All rights reserved. | 特定商取引法に基づく表示
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.