template magic

spir denis.spir at gmail.com
Tue Jan 25 07:21:29 PST 2011


Hello,

This post is about the various roles D templates can play. I had to write a 
higher-order function (hof) that takes as parameter a func which itself returns 
any kind of type. Thus, the hof is also templated. (Below the simplest case I 
could find as example.)

Unlike in functional style, in Phobos hof's often define their func parameter 
as a template parameter, not as a regular one. Following this example, I thus I 
ended up in something like this:

P[] hof1 (P, alias f) (uint n) {
     // just n times f's result
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f();
     return ps;
}
// example parameter function:
int f () { return 1; }

This runs fine. A conceptual issue that itched my brain is that the type 
parameter P is in fact defined twice, once explicitely & implicitely as f's 
return type. I thus re-placed f as an ordinary parameter:

P[] hof2 (P) (uint n, P function () f) {
     // same as above
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f();
     return ps;
}

This runs fine as well. But, guessing that Phobos's way of placing funcs as 
template parameters may be due to a good reason, I tried to do the same and 
still avoid P beeing defined twice. Used std.traits' ReturnType! for this:

auto hof3 (alias f) (uint n) {
     // below the magic
     alias ReturnType!f P;
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f();
     return ps;
}

As you see, this also requires using 'auto' for the hof's return type. But we 
can use the same magic trick there as well. Really ugly, but works:

ReturnType!f[] hof4 (alias f) (uint n) {
     alias ReturnType!f P;
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f();
     return ps;
}

The latter trick also solves the case where P / ReturnType!f is needed 
elsewhere in the hof's signature, eg:

ReturnType!f[] hof5 (alias f) (uint n, ReturnType!f p) {
     alias ReturnType!f P;
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f(p);
     return ps;
}

Can you believe all of those versions work fine? See whole test code below.
Now, 3 questions?

1. Is the fact that we have so many ways to define the same thing a Good Thing, 
according to you?

2. What is the reason for Phobos defining param funcs as template params? 
Efficiency? Why?

3. What is the meaning of 'alias f'? How does it work? This is a totally 
enigmatic feature for me. Seems it allows placing anything of any type as 
template param. Far more versatile than common use of templates as 'simple' 
generics. Note that if I remove the seemingly useless 'alias' from version 1 , 
I get:
     Error: template instance hof1!(int,f) does not match template declaration 
hof1(P,f)
???
(could not find any explanation on alias params anywhere -- pointers also welcome)

Denis

============ test code =================
import std.stdio;
import std.traits;

P[] hof1 (P, alias f) (uint n) {
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f();
     return ps;
}
P[] hof2 (P) (uint n, P function () f) {
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f();
     return ps;
}
auto hof3 (alias f) (uint n) {
     alias ReturnType!f P;
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f();
     return ps;
}
ReturnType!f[] hof4 (alias f) (uint n) {
     alias ReturnType!f P;
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f();
     return ps;
}
ReturnType!f[] hof5 (alias f) (uint n, ReturnType!f p) {
     alias ReturnType!f P;
     P[] ps;
     foreach (i ; 0..n)
         ps ~= f(p);
     return ps;
}

int f () { return 1; }
int f2 (int i) { return i; }

unittest {
     writeln( hof1!(int, f)(3) );

     writeln( hof2!int(3, &f) );
     writeln( hof2(3, &f) );

     writeln( hof3!f(3) );
     writeln( hof4!f(3) );

     writeln( hof5!f2(3, 1) );
}
void main () {}
=======================================
-- 
_________________
vita es estrany
spir.wikidot.com



More information about the Digitalmars-d-learn mailing list