【D言語】ForwardRangeのsaveとは何か?
今回は、ForwardRangeのsaveについて解説します。
まずはInputRangeについて見てみる
以下のようなコードを見てみます。
import std.stdio, std.range;
void main(){
auto range1 = iota(1, 10);
auto range2 = range1;
writeln(isInputRange!(typeof(range1))); // true
writeln(range1.front); // 1
range1.popFront();
writeln(range2.front); // 1
range2.popFront();
}
↑のコード内で使われている iota(a, b) は、a以上b未満の整数を並べたRangeを返す関数です。 iota が普通のRangeを返すため、このコードにな何も問題はありません。 しかし、以下のような特殊なRangeの場合はどうでしょう?
struct Range1{
int* value;
this(int i){
value = new int;
*value = 1;
}
@property int front(){ return *value; }
@property bool empty(){ return false; }
void popFront(){ ++(*value); }
}
このなんとも怪しげな挙動をしそうなRangeを↑のコードと同じように使ってみます。
import std.stdio, std.range;
void main(){
auto range1 = Range1(1);
auto range2 = range1;
writeln(isInputRange!(typeof(range1))); // true
writeln(range1.front); // 1
range1.popFront();
writeln(range2.front); // 2
range2.popFront();
}
isInputRangeがtrueを返しているなので、どちらもInputRangeであることは確かです。 なのに、違う結果になりました。いったいどうしてでしょう? 実は、InputRangeである range1 の popFront は、range2 に影響を与えることが出来ます。 下の例では、int型の値をヒープ上で共有しているので、range1.popFront が range2 に影響を与えています。
そこで出てくるForwardRange
ForwardRangeとは、InputRangeでありかつsaveを持つRangeのことです。 このsaveによって作られたRangeは、元のRangeのpopFrontの影響を受けません。 というか、そういう風にsaveを実装しなきゃいけません。 Range1にsaveを実装すると、以下のようになります。
struct Range1{
int* value;
this(int i){
value = new int;
*value = 1;
}
@property int front(){ return *value; }
@property bool empty(){ return false; }
@property Range1 save(){ return Range1(*value); }
void popFront(){ ++(*value); }
}
そして、このRange1を実際に使うと、
import std.stdio, std.range;
void main(){
auto range1 = Range1(1);
auto range2 = range1.save;
writeln(isForwardRange!(typeof(range1))); // true
writeln(range1.front); // 1
range1.popFront();
writeln(range2.front); // 1
range2.popFront();
}
となり、ちゃんと意図したとおりに動作してくれます。
まとめ
D言語の標準ライブラリであるPhobosは、Rangeを多用しています。 Phobosを使う上で、Rangeの種類をしっかりと把握しておくことはとても大切ですね。