Si el compilador de D encuentra un bucle foreach
foreach (element; range) {
// Cuerpo del bucle
}
este es reescrito internamente de forma similar a lo siguiente:
for (auto __rangeCopy = range;
!__rangeCopy.empty;
__rangeCopy.popFront())
{
auto element = __rangeCopy.front;
// Cuerpo del bucle
}
Se llama rango (range en inglés) a cualquier objeto que cumpla con la siguiente interfaz:
interface InputRange(E) {
bool empty();
E front();
void popFront();
}
Más concretamente esta interfaz es un InputRange
y es gracias a ella por
la que los elementos de los objetos de este tipo pueden ser iterados.
En el ejemplo de la derecha se puede ver la implementación y el uso de un rango de este tipo (input range).
Los rangos son perezosos (lazy en inglés), lo que significa que estos no son evaluados hasta que no es requerido. Es por esto por lo que se puede coger un número finito de elementos de un rango infinito:
42.repeat.take(3).writeln; // [42, 42, 42]
Si el objeto de tipo rango es un valor, el rango se copia y sólo esta copia será consumida en la iteración:
auto r = 5.iota;
r.drop(5).writeln; // []
r.writeln; // [0, 1, 2, 3, 4]
Si el objeto de tipo rango es una referencia (por ejemplo una clase declarada
mediante class
o un std.range.refRange
),
los valores del rango serán consumidos en la iteración y no podrán ser
restablecidos.
auto r = 5.iota;
auto r2 = refRange(&r);
r2.drop(5).writeln; // []
r2.writeln; // []
InputRanges
que se pueden copiar son ForwardRanges
Muchos de los rangos usados en la librería estándar son estructuras (struct
),
por lo que los bucles foreach
son generalmente no destructivos, aunque esto
no se garantiza. Si esta garantía es importante, se puede usar una
especialización de los InputRange
: rangos con el método .save
, también
conocidos como ForwardRange
:
interface ForwardRange(E) : InputRange!E {
typeof(this) save();
}
// por valor (estructuras)
auto r = 5.iota;
auto r2 = refRange(&r);
r2.save.drop(5).writeln; // []
r2.writeln; // [0, 1, 2, 3, 4]
ForwardRanges
se pueden extender para ser bidireccionales y con acceso aleatorio a elementosHay dos extensiones de los ForwardRange
: (1) un rango bidireccional y (2) un
rango con acceso aleatorio a sus elementos.
Un rango bidireccional permite la iteración desde el final:
interface BidirectionalRange(E) : ForwardRange!E {
E back();
void popBack();
}
5.iota.retro.writeln; // [4, 3, 2, 1, 0]
Un rango con acceso aleatorio a elementos tiene una longitud conocida,
length
, y se puede acceder a cada elemento de forma directa:
interface RandomAccessRange(E) : ForwardRange!E {
E opIndex(size_t i);
size_t length();
}
Los rangos con acceso aleatorio a elementos más famosos son los arrays de D.
auto r = [4, 5, 6];
r[1].writeln; // 5
Las funciones en std.range
y
std.algorithm
proporcionan
bloques de construcción que hacen uso de esta interfaz de rangos. Los rangos
permiten componer complejos algoritmos detrás de objetos sobre los que se
itera con facilidad. Además, los rangos permiten crear objetos perezosos
(lazy en inglés) que sólo realizan cálculos cuando realmente se necesitan
en la iteración, es decir, cuando se accede al siguiente elemento del rango.
Algoritmos especiales sobre rangos se verán en la sección
perlas de D.