D言語で無限レンジを作るときに注意しなければならないこと。 ドキュメントをちゃんと読み込んでなかったので長らく気づきませんでした。
無限レンジ
無限レンジとは要素数が無限のレンジです。 例えばこれは1が無限に続くレンジです。
struct One {
long front(){ return 1; }
void popFront() {}
bool empty()
{
return false;
}
}
こんな感じにtake
とかと組み合わせて使います。
import std.range : take;
import std.stdio : writeln;
void main()
{
One().take(10).writeln(); // [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}
isInfinite!One == false
しかしここでisInfinite
を使ってみるとfalse
になります。
assert(isInfinite!One); // Assertion failure
今まで使ってきたなかでこれで困ったことはありませんが、ライブラリを作るときや、 契約で無限レンジのみを受け取るようにしている関数を使うようなことがあるとマズいです。
enumを使う
empty
が定数false
を返しているので無限レンジとして成り立っているように思えますが、
これはコンパイル時的にはアウトのようです。
この関数が常にfalse
を返すのか確かめるすべがありません。
関数ではなくenum
を使ってコンパイル時定数にしてやる必要があります。
struct Two {
long front(){ return 2; }
void popFront() {}
enum bool empty = false;
}
これでisInfinite
はtrue
になります。
assert(isInfinite!Two); // Pass
今までずっと関数で書いてたので、自分が書いていたものは何ひとつ"Infinite range"ではなかったということになります。
調べてみたらdlang-tourも11月まで間違えて
const
にしていました。
const
が使えなくなったのはDMD 2.067.1からのようです。
ちなみに関数のempty
がisInfinite
に認められていた時期は一度もありません。
追記: 重要なのは静的にわかるかどうか
staticをつけてもいいですよ https://t.co/VSmGIT59AM
— karita (@kari_tech) May 27, 2019
というわけでstatic
関数なら大丈夫です。
struct Two {
long front(){ return 2; }
void popFront() {}
static bool empty()
{
return false;
}
}
import std.range : isInfinite;
static assert(isInfinite!Two); // Pass
isInfinite
の中身を見てみればどういうことか理解できます。
というか真っ先に読んでみるべきでしたね……
phobos/primitives.d at v2.086.0 · dlang/phobos
/**
Returns `true` if `R` is an infinite input range. An
infinite input range is an input range that has a statically-defined
enumerated member called `empty` that is always `false`,
for example:
----
struct MyInfiniteRange
{
enum bool empty = false;
...
}
----
*/
template isInfinite(R)
{
static if (isInputRange!R && __traits(compiles, { enum e = R.empty; }))
enum bool isInfinite = !R.empty;
else
enum bool isInfinite = false;
}
見ての通りisInfinite
は非常にシンプルなテンプレートです。
isInfinite
がtrue
になるのは、インプットレンジR
についてenum e = R.empty;
がコンパイルできる、
つまりR.empty
にコンパイル時にアクセスできるときで、なおかつR.empty
がfalse
になるときです。
最初のコードは普通のメンバ関数がインスタンスなしに直接R.empty
を呼び出せないのでfalse
になっていたというわけですね。
もちろんstatic
関数でもコンパイル時に決まらないような内容だとfalse
になります。
単にfalse
を返すだけなら行数が増えるだけなので通常はenum
を使えばいいと思います。
static
関数はテンプレート引数によって無限か有限かが切り替わるようなレンジを作るときに役に立つでしょうか?