【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の種類をしっかりと把握しておくことはとても大切ですね。