Range (Intervalli)

Quando il compilatore incontra un foreach

foreach (element; range)
{
    // Corpo del ciclo...
}

viene internamente riscritto in modo simile a questo:

for (auto __rangeCopy = range;
     !__rangeCopy.empty;
     __rangeCopy.popFront())
 {
    auto element = __rangeCopy.front;
    // Corpo del ciclo...
}

Qualsiasi oggetto che soddisfa la seguente interfaccia è chiamato range (o più specificamente InputRange) ed è quindi un tipo su cui si può iterare:

    interface InputRange(E)
    {
        bool empty();
        E front();
        void popFront();
    }

Dai un'occhiata all'esempio sulla destra per esaminare più da vicino l'implementazione e l'utilizzo di un input range.

Lazy Evaluation (Valutazione Pigra)

I range sono pigri. Non verranno esaminati fino a quando non richiesto. Quindi, è possibile prendere un range da un range infinito:

42.repeat.take(3).writeln; // [42, 42, 42]

Tipi per Valore vs. Tipi per Riferimento

Se l'oggetto range è un tipo per valore, allora il range verrà copiato e solo la copia sarà consumata:

auto r = 5.iota;
r.drop(5).writeln; // []
r.writeln; // [0, 1, 2, 3, 4]

Se l'oggetto range è un tipo per riferimento (es. class o std.range.refRange), allora il range verrà consumato e non sarà ripristinato:

auto r = 5.iota;
auto r2 = refRange(&r);
r2.drop(5).writeln; // []
r2.writeln; // []

Gli InputRange Copiabili sono ForwardRange

La maggior parte dei range nella libreria standard sono struct e quindi l'iterazione foreach è solitamente non distruttiva, anche se non garantita. Se questa garanzia è importante, si può usare una specializzazione di un InputRange— i ForwardRange con un metodo .save:

interface ForwardRange(E) : InputRange!E
{
    typeof(this) save();
}
// per valore (Structs)
auto r = 5.iota;
auto r2 = refRange(&r);
r2.save.drop(5).writeln; // []
r2.writeln; // [0, 1, 2, 3, 4]

I ForwardRange possono essere estesi a range bidirezionali + range ad accesso casuale

Ci sono due estensioni del ForwardRange copiabile: (1) un range bidirezionale e (2) un range ad accesso casuale. Un range bidirezionale permette l'iterazione dalla fine:

interface BidirectionalRange(E) : ForwardRange!E
{
     E back();
     void popBack();
}
5.iota.retro.writeln; // [4, 3, 2, 1, 0]

Un range ad accesso casuale ha una length nota e ogni elemento può essere acceduto direttamente.

interface RandomAccessRange(E) : ForwardRange!E
{
     E opIndex(size_t i);
     size_t length();
}

Il range ad accesso casuale più conosciuto è l'array di D:

auto r = [4, 5, 6];
r[1].writeln; // 5

Algoritmi pigri per i range

Le funzioni in std.range e std.algorithm forniscono i blocchi di costruzione che utilizzano questa interfaccia. I range permettono la composizione di algoritmi complessi dietro un oggetto che può essere iterato con facilità. Inoltre, i range permettono la creazione di oggetti pigri che eseguono un calcolo solo quando è realmente necessario in un'iterazione, ad esempio quando si accede al prossimo elemento del range. Gli algoritmi speciali per i range saranno presentati più avanti nella sezione Gemme di D.

Approfondimenti

rdmd playground.d