The opApply() recipe
The D programming language climbed to #27 in Tiobe’s Programming Community Index for June 2010. Here with some tips on this up-and-coming language is our very own David Eckardt.
In D, a class or struct can provide foreach iteration over its instances by implementing a special method named opApply(). This is an extremely useful feature; however, understanding the semantics of opApply is pretty hard if you are new to it. Unfortunately, the description on the Digital Mars page is too brief to get the point, while the famous book Learn to Tango with D announces it, but then completely lacks the section.
But the use of opApply() can be explained as a recipe, and here it goes. In the examples we will use a struct; however, opApply()ing a class is done exactly the same way.
1. Write a simple class or struct and consider the number and types of the iteration variables. It is possible to iterate over any number — at least one — of variables of arbitrary types. For example, we want to iterate over one int value:
foreach (x; instance)
{
// x is of type int; instance is of the type of our struct
}
2. Add a method named opApply() to the class/struct and follow these rules:
a. The return type of opApply() is int.
b. opApply() takes exactly one argument. That argument is a delegate and must conform to this:
b1. The return value of the delegate is int.
b2. The argument list of the delegate corresponds to the iteration variables where all arguments must have the ref attribute.
For the foreach iteration example above, the opApply() function declaration would be:
int opApply ( int delegate ( ref int x ) dg );
Again, the return type of opApply() and the delegate (named dg in the example) is mandatory while the argument list of the delegate — one argument x of type opApply() — corresponds to the iteration variables. Note that the delegate arguments don’t really need identifiers, hence
int opApply ( int delegate ( ref int ) dg );
is equivalent. The
3. Make opApply() behave as mandated by D. That means:
a. For each foreach iteration cycle invoke the delegate. The arguments passed to it appear as the iteration variables in the foreach loop body.
b. Store the return value of the delegate in a local variable.
b1. DO NOT think about the meaning of that value.
b2. DO NOT change that value. Just memorize it.
c. If the return value of the delegate is equal to 0, you may invoke the delegate again. Otherwise, if the value differs from 0, the delegate must not be invoked any more.
d. opApply() returns the return value of the delegate or 0 if the delegate was not invoked.
That’s it! Completing our example:
module iterator;
/*
* #1: struct providing iteration
*/
struct MyIterator
{
int[] arr = [2, 5, 6];
/*
* #2: opApply() method with return type and delegate argument
* as mandated
*/
int opApply ( int delegate ( ref int x ) dg )
{
/*
* #3b: Local variable "result" to store the return
* value of the delegate
*/
int result = 0;
foreach (ref x; this.arr)
{
/*
* #3a: Invoke delegate, store return value.
*/
result = dg(x);
/*
* #3c: Continue only if result == 0, otherwise
* break.
*/
if (result) break;
}
/*
* #3d: Return delegate return value or 0 if the
* delegate has not been invoked-
*/
return result;
}
}
void main ( )
{
MyIterator instance;
foreach (x; instance)
{
// x iterates over [2, 5, 6]
}
/*
* Adding the ref attribute to an iteration variable provides
* write access to the actual argument passed to the delegate
* on invocation by opApply(). We use that to change the "arr"
* array property of "MyIterator".
*/
foreach (ref x; instance)
{
// x iterates over [2, 5, 6]
if (x == 5)
{
x = 3;
}
}
foreach (x; instance)
{
// x now iterates over [2, 3, 6]
}
}
The opApply() method does not necessarily have to contain loop, although it will in most cases. A struct or class can iterate over its own instance:
class MyIteratorClass
{
int opApply ( int delegate ( ref int x ) dg )
{
// do some iteration
}
void iterate ( )
{
/*
* Iterate using this.opApply().
*/
foreach (x; this)
{
// ...
}
}
}
class MyIteratorStruct
{
int opApply ( int delegate ( ref int x ) dg )
{
// do some iteration
}
void iterate ( )
{
/*
* Iterate using this.opApply(). In contrast to
* classes, in structs 'this' is a pointer to this
* instance.
*/
foreach (x; *this)
{
// ...
}
}
}
Iteration can be forwarded by passing through the delegate:
struct MyIterator
{
int opApply ( int delegate ( ref int x ) dg )
{
// do some iteration
}
}
struct YourIterator
{
MyIterator my_iter;
int opApply ( int delegate ( ref int x ) dg )
{
/*
* Forward iteration to my_iter property.
*/
return this.my_iter.opApply(dg);
}
}
