www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - Futurism lib (futures in D)

reply Kevin Bealer <kevinbealer gmail.com> writes:
I posted my future implementation dsource.org.  It's called "Futurism".  Here
is a simple example of using it:

int main()
{
    // Create a pool of 5 threads
    ThreadPool P = new ThreadPool(5);
    scope(exit) P.stop;

    alias Future!(char[]) FVec;

    char[] a = args[1], b = args[2], c = args[3];

    // Starting reading two files at once:
    FVec f1 = new FVec({ return cast(char[]) read(a); });
    FVec f2 = new FVec({ return cast(char[]) read(b); });



    int total_length = f1.value.length + f2.value.length;

    writefln("total length is %s", f1.value.length + );

    return 0;
}
Jan 21 2007
next sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
Sorry - the last post got posted early due to an errant keystroke.  The code
should look like this:

 int main()
 {
     // Create a pool of 5 threads
     ThreadPool P = new ThreadPool(5);
     scope(exit) P.stop;

     alias Future!(char[]) FVec;
     char[] a = args[1], b = args[2], c = args[3];

     // Starting reading two files at once:
     FVec f1 = new FVec({ return cast(char[]) read(a); });
     FVec f2 = new FVec({ return cast(char[]) read(b); });

     // You can do other stuff here if you like
     writefln("files are being read");

     // This line will wait for both files to be read.
     int total_length = f1.value.length + f2.value.length;
     writefln("total length is %s", f1.value.length + );

     return 0;
 }

It took me a bit longer than I expected, when I accidentally did this: dmd -of~/somefile ... And ended up with directory called "~". Naturally I did "rm -rvf ~". There wasn't much else in my home directory right now (new computer), but I was able to recover the source for this stuff using "strings < /dev/hda5" and some elaborate "grep" commands. Try it out if you like, let me know if it has bugs or missing features. Thanks, Kevin
Jan 21 2007
parent reply "Saaa" <empty needmail.com> writes:
I read wikipedia about future and it sounds interesting.
Do you have a bit more information, or some place where I can get it?

As I read it, futurism takes care of threading and synchronization of data 
(between threads). 
Jan 21 2007
next sibling parent "Saaa" <empty needmail.com> writes:
:)
http://www.dsource.org/projects/futurism/browser/trunk/futurism/readme.txt
Jan 21 2007
prev sibling next sibling parent Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Saaa (empty needmail.com)'s article
 I read wikipedia about future and it sounds interesting.
 Do you have a bit more information, or some place where I can get it?
 As I read it, futurism takes care of threading and synchronization of data
 (between threads).

Let's say I needed to find the sum of an array. int sum(int[] x) { int rv = 0; foreach(int y; x) { rv += y; } return rv; } On a multicpu machine, this operation will run on one CPU. With futures, I can write another routine that utilizes up to 4 CPUs: int sum_4(int[] x) { alias Future!(int) FInt; int part = x.length/4; // Each of these queues a work item to be done either on some // other thread, or by the main thread here. FInt x1 = new FInt({ return sum(x[0..part]); }); FInt x2 = new FInt({ return sum[part..part*2]); }); FInt x3 = new FInt({ return sum[part*2..part*3]); }); FInt x4 = new FInt({ return sum[part*3..$]; }); // The addition below waits for each results to complete // before adding it to the total. return x1.value + x2.value + x3.value + x4.value; } In this case, the four futures will split the array into four parts and queue each quarter as a work item. The four parts will be done in parallel. It should take about 1/4 as long, minus some overhead for building the future objects and so on. The idea is that any time a function needs to do several things that don't depend on each other, they can be represented as futures and the program will run in parallel. My motivation for writing this was a talk by Herb Sutter, its available on Google video here: http://video.google.com/videoplay?docid=7625918717318948700&q=sutter It's pretty long, but interesting I thought -- his basic argument is that computers have gotten faster up till now mostly because the CPU is higher megahertz with better pipelining. But from now on, computers will run faster mostly because they have a lot of CPUs or cores working together. We are seeing the first of this with the dual core and quad core CPUs they are producing now. But the *software* won't actually run any faster unless the programs can be written in a multithreaded way. You will have the same "Photoshop" running at the same speed on one CPU, with other CPUs idling away doing nothing. The problem is that it's hard to write good applications that take advantage of the extra CPUs. It's very hard to write good code with just Thread and Mutex semantics, and monitor/sleep is only a little better. Worse, if I write a library that uses mutexes to synchronize access, I can't really combine it with another library that does the same thing, because to design mutex-using code that doesn't deadlock, you need to know everything about the locking patterns for the whole application. If you combine two libraries that can't deadlock, the result often *will* have potential deadlocks. But, with the future idiom, you can write code that uses extra threads to do work whenever you have two calculations that don't depend on each other. In the example above, the sum of four arrays can be computed seperately. In the Futurism source code on dsource, there is a qsort algorithm that uses futures to do work in parallel. Kevin
Jan 21 2007
prev sibling parent reply Mikola Lysenko <mclysenk mtu.edu> writes:
Saaa wrote:
 
 As I read it, futurism takes care of threading and synchronization of data 
 (between threads). 
 
 

Futures are a powerful technique to increase the parallelism of an application without much extra programming effort. But as much as I like futures, it is Pollyannaish to claim that they solve all thread related problems. Proper use of futures prevents the future threads from communicating altogether eliminating concurrency. This is both good and bad; on the one hand concurrent programming is always risky business, yet in most situations involving threads concurrency is unavoidable. A classic example is the dining philosophers problem. Each philosopher must communicate in order to share common resources.
Jan 21 2007
parent Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Mikola Lysenko (mclysenk mtu.edu)'s article
 Saaa wrote:
 As I read it, futurism takes care of threading and synchronization of data
 (between threads).

Futures are a powerful technique to increase the parallelism of an application without much extra programming effort. But as much as I like futures, it is Pollyannaish to claim that they solve all thread related problems. Proper use of futures prevents the future threads from communicating altogether eliminating concurrency. This is both good and bad; on the one hand concurrent programming is always risky business, yet in most situations involving threads concurrency is unavoidable. A classic example is the dining philosophers problem. Each philosopher must communicate in order to share common resources.

I see some truth in this. I also think the dining philosopher problem would be less difficult if you have a controlling algorithm that can arbitrate between the three philosophers -- in other words, the problem is difficult because it is formulated as a peer-to-peer design. I think there are tasks that futures don't solve. For example, if you want a status report written to a disk file every N seconds, or you need to control access to a large central hash table, then futures are not the normal solution to those kinds of problems. But I don't see a conflict - I think you can have a status report thread, a mutex-serialized hash table, and still use futures to multithread the complex work loads. There are different uses for multithreaded designs. One of these is to break up complex work, and I think this is where mutexes are awkward and futures are elegant. Kevin Bealer
Jan 22 2007
prev sibling parent reply Daniel Keep <daniel.keep+lists gmail.com> writes:
Kevin Bealer wrote:
 I posted my future implementation dsource.org.  It's called "Futurism".  Here
 is a simple example of using it:
 
 int main()
 {
     // Create a pool of 5 threads
     ThreadPool P = new ThreadPool(5);
     scope(exit) P.stop;
 
     alias Future!(char[]) FVec;
 
     char[] a = args[1], b = args[2], c = args[3];
 
     // Starting reading two files at once:
     FVec f1 = new FVec({ return cast(char[]) read(a); });
     FVec f2 = new FVec({ return cast(char[]) read(b); });
 
 
 
     int total_length = f1.value.length + f2.value.length;
 
     writefln("total length is %s", f1.value.length + );
 
     return 0;
 }
 

Looks good; more sophisticated than my own. A few initial thoughts: 1) Have you considered stealing my code and using the "future(...)" syntax I used? It seems a bit cleaner than having to alias the class, and then instantiating objects. 2) You say that ThreadPools must be stopped before being deleted. Maybe it would be good to have a ThreadPoolScope class that must be scoped, and will call stop for you. 3) Speaking of the thread pools, have you considered creating a default pool with (number of hardware threads) threads? I think std.cpuid can give you those numbers, but I don't know enough to say whether it would a good idea performance-wise. Again, it looks really good. Might have to start using it myself :) -- Daniel
Jan 21 2007
next sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s article
 Kevin Bealer wrote:

 Looks good; more sophisticated than my own.  A few initial thoughts:

 1) Have you considered stealing my code and using the "future(...)"
 syntax I used?  It seems a bit cleaner than having to alias the class,
 and then instantiating objects.

I wanted the first checkin to be "stealing free" ;), but I was actually going to ask if you minded me adapting the tuples interface from yours. I guess not, so I'll look at doing that then.
 2) You say that ThreadPools must be stopped before being deleted.  Maybe
 it would be good to have a ThreadPoolScope class that must be scoped,
 and will call stop for you.

Couldn't hurt.
 3) Speaking of the thread pools, have you considered creating a default
 pool with (number of hardware threads) threads?  I think std.cpuid can
 give you those numbers, but I don't know enough to say whether it would
 a good idea performance-wise.

I'm not sure about the design. I originally had thread pools call stop() from its own destructor, but of course that doesn't work because the Thread object can be garbage collected first. What I'm not sure of is this: all objects are garbage collected at program termination, but static destructors (static ~this()) blocks are also called at program termination. Is there a guarantee that the GC runs after the ~this() blocks? I would also recommend using more thread pool threads that hardware threads, because some operations (like reading files) are IO bound, and the system benefits if it can switch to a CPU bound thread. So you always want at least the number of hardware threads, but I think its a matter of tuning how many more than that. If you think each task is about 50% CPU bound, you might want something like 2x the number of hardware threads.
 Again, it looks really good.  Might have to start using it myself :)
 	-- Daniel

Thanks - I appreciate the suggestions, too. Kevin
Jan 21 2007
parent reply "Kristian Kilpi" <kjkilpi gmail.com> writes:
On Mon, 22 Jan 2007 03:19:53 +0200, Kevin Bealer <kevinbealer gmail.com>=
  =

wrote:
[snip]
 I would also recommend using more thread pool threads that hardware  =

 threads,
 because some operations (like reading files) are IO bound, and the  =

 system benefits
 if it can switch to a CPU bound thread.  So you always want at least t=

 number of
 hardware threads, but I think its a matter of tuning how many more tha=

 that.  If
 you think each task is about 50% CPU bound, you might want something  =

 like 2x the
 number of hardware threads.

Nice job! I'm looking forward to use futures in the future. :) BTW, is there a way to know the number of hardware threads (and maybe th= e = number of CPUs)? IMHO it would be good to have something like this too: new ThreadPool(4); //pool of 4 threads new ThreadPool(); //pool of X threads, where X is the # of hardwa= re = threads new ThreadPool(1.5); //pool of 1.5 * X threads (>=3D 1, rounded up = perhaps) (I think one should, in general, always use thread counts relative to th= e = number of hardware threads.)
Jan 23 2007
next sibling parent reply =?ISO-8859-1?Q?Jari-Matti_M=E4kel=E4?= <jmjmak utu.fi.invalid> writes:
Kristian Kilpi kirjoitti:
 BTW, is there a way to know the number of hardware threads (and maybe
 the number of CPUs)?

std.cpuid has threadsPerCPU(). See http://www.digitalmars.com/d/phobos/std_cpuid.html. How does one know how many hw threads there are available, if two multi-threaded programs are running?
Jan 23 2007
parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Jari-Matti_Mäkelä (jmjmak utu.fi.invalid)'s article
 Kristian Kilpi kirjoitti:
 BTW, is there a way to know the number of hardware threads (and maybe
 the number of CPUs)?

std.cpuid has threadsPerCPU(). See http://www.digitalmars.com/d/phobos/std_cpuid.html. How does one know how many hw threads there are available, if two multi-threaded programs are running?

My tendency would be for each program to use enough threads to utilize all the hardware threads. This means that there will be more threads running than CPUs exist, but I don't see extra threads as especially harmful. This is even more true if one or the other application is sometimes idle. For example, if a multithreaded web browser is running, it might use all the CPUs when the user surfing, but after each page loads, the threads will all stop while the user reads the page. I think this is the case for most programs on a desktop machine. For server farms, more tuning is required, and most workloads are already pretty well optimized for the specific hardware. (But see also the latest changes to futurism.) Kevin
Jan 24 2007
parent =?ISO-8859-1?Q?Jari-Matti_M=E4kel=E4?= <jmjmak utu.fi.invalid> writes:
Kevin Bealer kirjoitti:
 == Quote from Jari-Matti_Mäkelä (jmjmak utu.fi.invalid)'s article
 Kristian Kilpi kirjoitti:
 BTW, is there a way to know the number of hardware threads (and maybe
 the number of CPUs)?

http://www.digitalmars.com/d/phobos/std_cpuid.html. How does one know how many hw threads there are available, if two multi-threaded programs are running?

My tendency would be for each program to use enough threads to utilize all the hardware threads. This means that there will be more threads running than CPUs exist, but I don't see extra threads as especially harmful. This is even more true if one or the other application is sometimes idle.

Yeah, this is probably the best for desktop use, where the machine is idle or running just one CPU intensive program (e.g. a game) most of the time.
 I think this is the case for most programs on a desktop machine.  For server
 farms, more tuning is required, and most workloads are already pretty well
 optimized for the specific hardware.

Yup.
 
 (But see also the latest changes to futurism.)

Very nice. This is now much better than creating verbose worker constructs for every parallel operation (Java) or overly complex FSM's using interrupts and timers like in the good old times (DOS games).
Jan 24 2007
prev sibling next sibling parent Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Kristian Kilpi (kjkilpi gmail.com)'s article
 On Mon, 22 Jan 2007 03:19:53 +0200, Kevin Bealer <kevinbealer gmail.com>=

 Nice job! I'm looking forward to use futures in the future. :)
 BTW, is there a way to know the number of hardware threads (and maybe the
 number of CPUs)?

 IMHO it would be good to have something like this too:
    new ThreadPool(4);    //pool of 4 threads
    new ThreadPool();     //pool of X threads, where X is the # of hardwa=
 re  =
 threads
    new ThreadPool(1.5);  //pool of 1.5 * X threads (>=3D 1, rounded up  =
 perhaps)
 (I think one should, in general, always use thread counts relative to th=
 e  =
 number of hardware threads.)

I added something like this to Futurism; it uses five criteria to pick the number of threads to use, in this order: 1. Value passed to constructor (if any). 2. Number from envar FUTURISM_DEFAULT_THREADS (if any). 3. On linux, use #cores * overlap from /proc/cpuinfo. 4. On other x86, use std.cpuid.threadsPerCore * overlap. 5. If all else fails, assume 2 cpus * overlap. Here, overlap is defined as 4, so typically you will see 4 threads on a single CPU machine or 8 on a dual core. The envar is there so that people with several multithreaded apps can balance their own system load if they like. All numbers except for #1 will actually use N-1 worker threads, because the library is designed so that the main thread pulls some of the weight. Kevin
Jan 24 2007
prev sibling parent reply Walter Bright <newshound digitalmars.com> writes:
Kristian Kilpi wrote:
 (I think one should, in general, always use thread counts relative to 
 the number of hardware threads.)

Multithreaded code can speed up computation even on a machine with only one CPU, because of how I/O blocking works.
Jan 26 2007
parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Walter Bright wrote:
 Kristian Kilpi wrote:
 (I think one should, in general, always use thread counts relative to 
 the number of hardware threads.)

Multithreaded code can speed up computation even on a machine with only one CPU, because of how I/O blocking works.

So? You may still want twice as many threads if you've got twice the hardware threads, which is what Kristian was saying. "relative to" != "equal to" :p.
Jan 26 2007
parent Walter Bright <newshound digitalmars.com> writes:
Frits van Bommel wrote:
 Walter Bright wrote:
 Kristian Kilpi wrote:
 (I think one should, in general, always use thread counts relative to 
 the number of hardware threads.)

Multithreaded code can speed up computation even on a machine with only one CPU, because of how I/O blocking works.

So? You may still want twice as many threads if you've got twice the hardware threads, which is what Kristian was saying. "relative to" != "equal to" :p.

Ok.
Jan 26 2007
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s article
 Kevin Bealer wrote:

 int main()
 {
     // Create a pool of 5 threads
     ThreadPool P = new ThreadPool(5);
     scope(exit) P.stop;

     alias Future!(char[]) FVec;

     char[] a = args[1], b = args[2], c = args[3];

     // Starting reading two files at once:
     FVec f1 = new FVec({ return cast(char[]) read(a); });
     FVec f2 = new FVec({ return cast(char[]) read(b); });



     int total_length = f1.value.length + f2.value.length;

     writefln("total length is %s", f1.value.length + );

     return 0;
 }

1) Have you considered stealing my code and using the "future(...)" syntax I used? It seems a bit cleaner than having to alias the class, and then instantiating objects.

By the way, I've done this part -- I wrote a function called make_future, (can't use "future" since thats the module name) and modified the templates in future.d to store the argument tuples. There is still things I'm not sure about: 1. Why is a foreach() loop needed to copy a tuple value to another tuple of the same type? 2. You can't pass a char[10] (for example) to a function that uses a char[] input -- instead, you need to ask for the slice, for example, using syntax like: make_future(& work, 1234, "some string"[]); ^^ I could probably fix this if I knew how to use static if or "is" to find out whether something is a static array or not. For now, the above syntax is a not-too-painful workaround. Kevin
Jan 22 2007
next sibling parent reply Daniel Keep <daniel.keep+lists gmail.com> writes:
Kevin Bealer wrote:
 == Quote from Daniel Keep (daniel.keep+lists gmail.com)'s article
 
Kevin Bealer wrote:
Daniel Keep wrote a whole buncha junk, *yoink!*

Looks good; more sophisticated than my own. A few initial thoughts: 1) Have you considered stealing my code and using the "future(...)" syntax I used? It seems a bit cleaner than having to alias the class, and then instantiating objects.


P.S. Of course I don't mind; I'm happy to be able to contribute!
 By the way, I've done this part -- I wrote a function called make_future,
 (can't use "future" since thats the module name) and modified the templates
 in future.d to store the argument tuples.
 
 There is still things I'm not sure about:
 
 1. Why is a foreach() loop needed to copy a tuple value to another tuple of
    the same type?

I tried assigning directly, and it didn't like that, hence the foreach loop. Since it's working on template arguments, it *should* unroll at compile time.
 2. You can't pass a char[10] (for example) to a function that uses a char[]
    input -- instead, you need to ask for the slice, for example, using
    syntax like:
 
      make_future(& work, 1234, "some string"[]);
                                             ^^
 
    I could probably fix this if I knew how to use static if or "is" to find
    out whether something is a static array or not.  For now, the above syntax
    is a not-too-painful workaround.
 
 Kevin

Ah yes, I've had a few problems with this myself (in other things). The trick here is that instead of specialising on the type of the function, I'm specialising on the type of the arguments supplied. In most cases, this isn't a problem, but it does cause problems for arrays (and possibly a few other cases). (I think that) what you need to do is to check to see if an argument is an array, and then check the corresponding type in the function call, and make any necessary casts/conversions. Of course, the alternative is to move the function out to the template argument as an alias, and THEN specialise on the argument type of that (which I've done in my OGL/SDL safety templates), which would make it look like this: make_future!(work)(1234, "some string"); At least, I think it would :P This is one of the reasons I love D: programming in it is like opening up a shiny russian doll: every time you think you know what's going on, there's a whole 'nother layer to play with. Oh hang on, that was supposed to be the "peeling an onion" metaphor, wasn't it? Oh well... -- Daniel "No, ogres are NOT like cake!"
Jan 22 2007
next sibling parent Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s article
 Kevin Bealer wrote:

 I tried assigning directly, and it didn't like that, hence the foreach
 loop.  Since it's working on template arguments, it *should* unroll at
 compile time.

I went through the same thing - but probably quicker because I could look at your example. It's not a problem; maybe its just a temporary limitation.
 2. You can't pass a char[10] (for example) to a function that uses a char[]
    input -- instead, you need to ask for the slice, for example, using
    syntax like:

      make_future(& work, 1234, "some string"[]);
                                             ^^

    I could probably fix this if I knew how to use static if or "is" to find
    out whether something is a static array or not.  For now, the above syntax
    is a not-too-painful workaround.

 Kevin

Ah yes, I've had a few problems with this myself (in other things). The trick here is that instead of specialising on the type of the function, I'm specialising on the type of the arguments supplied. In most cases, this isn't a problem, but it does cause problems for arrays (and possibly a few other cases). (I think that) what you need to do is to check to see if an argument is an array, and then check the corresponding type in the function call, and make any necessary casts/conversions.

I'll look into this angle. I imagine it will require some type-list style tuple recursion.
 Of course, the alternative is to move the function out to the template
 argument as an alias, and THEN specialise on the argument type of that
 (which I've done in my OGL/SDL safety templates), which would make it
 look like this:

      make_future!(work)(1234, "some string");

 At least, I think it would :P

I'm going to try to get the other one smoothed out - it looks a little cleaner to me right now.
 This is one of the reasons I love D: programming in it is like opening
 up a shiny russian doll: every time you think you know what's going on,
 there's a whole 'nother layer to play with.

"Learning is fun!" -- Bender
 Oh hang on, that was supposed to be the "peeling an onion" metaphor,
 wasn't it?  Oh well...

 	-- Daniel "No, ogres are NOT like cake!"

Thanks, Kevin
Jan 22 2007
prev sibling parent reply Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s article
 Kevin Bealer wrote:

 2. You can't pass a char[10] (for example) to a function that uses a char[]
    input -- instead, you need to ask for the slice, for example, using
    syntax like:

      make_future(& work, 1234, "some string"[]);
                                             ^^

    I could probably fix this if I knew how to use static if or "is" to find
    out whether something is a static array or not.  For now, the above syntax
    is a not-too-painful workaround.

 Kevin

Of course, the alternative is to move the function out to the template argument as an alias, and THEN specialise on the argument type of that (which I've done in my OGL/SDL safety templates), which would make it look like this: make_future!(work)(1234, "some string"); At least, I think it would :P This is one of the reasons I love D: programming in it is like opening up a shiny russian doll: every time you think you know what's going on, there's a whole 'nother layer to play with.

I didn't fully appreciate this statement until I got the solution working. Now my head hurt like back when I was learning recursion for the first time, which is a sign that my mental software is getting a refactoring. ;) Template metaprogramming requires another layer of meta-thinking. Practice helps too (with the syntax especially). I think I'm finally starting to "absorb" what tuples are doing and why variadic stuff works the way it does. The requirement for "[]" is gone now, but more fundamentally, conversions between arguments and parameters should happen in the 'right direction'. This is accomplished by extracting the parameters from the delegate inside of make_future() (using a helper template). The changes are in SVN but for those of you just reading along, these are the modified lines: template FutureTypeGroup(Delegate, Args...) { alias ReturnType!(Delegate) Return; alias ParameterTypeTuple!(Delegate) Params; alias Future!(Return, Params) TFuture; } FutureTypeGroup!(Delegate, Args).TFuture make_future(Delegate, Args...)(Delegate cmd, Args args) { return new FutureTypeGroup!(Delegate, Args).TFuture(cmd, args); } I think the fact that this change affects so few lines is a testament to the increasing expressiveness of D. Kevin
Jan 22 2007
parent Daniel Keep <daniel.keep+lists gmail.com> writes:
Kevin Bealer wrote:
 == Quote from Daniel Keep (daniel.keep+lists gmail.com)'s article
 
Of course, the alternative is to move the function out to the template
argument as an alias, and THEN specialise on the argument type of that
(which I've done in my OGL/SDL safety templates), which would make it
look like this:

     make_future!(work)(1234, "some string");

At least, I think it would :P

This is one of the reasons I love D: programming in it is like opening
up a shiny russian doll: every time you think you know what's going on,
there's a whole 'nother layer to play with.

I didn't fully appreciate this statement until I got the solution working. Now my head hurt like back when I was learning recursion for the first time, which is a sign that my mental software is getting a refactoring. ;) Template metaprogramming requires another layer of meta-thinking. Practice helps too (with the syntax especially). I think I'm finally starting to "absorb" what tuples are doing and why variadic stuff works the way it does.

I know the feeling. There's nothing quite like the moment when something new finally clicks into place.
 The requirement for "[]" is gone now, but more fundamentally, conversions
 between arguments and parameters should happen in the 'right direction'.
 
 This is accomplished by extracting the parameters from the delegate inside
 of make_future() (using a helper template).  The changes are in SVN but for
 those of you just reading along, these are the modified lines:
 
 template FutureTypeGroup(Delegate, Args...) {
     alias ReturnType!(Delegate)          Return;
     alias ParameterTypeTuple!(Delegate)  Params;
     alias Future!(Return, Params)        TFuture;
 }
 
 FutureTypeGroup!(Delegate, Args).TFuture
 make_future(Delegate, Args...)(Delegate cmd, Args args)
 {
     return new FutureTypeGroup!(Delegate, Args).TFuture(cmd, args);
 }

Now, see, I hadn't thought of trying that: using another level of indirection to coax the compiler into handling the conversions itself. Very nice.
 I think the fact that this change affects so few lines is a testament
 to the increasing expressiveness of D.
 
 Kevin

That, and our ability to abuse^Hmake use of some of D's advanced features :) -- Daniel
Jan 23 2007
prev sibling parent reply Lutger <lutger.blijdestijn gmail.com> writes:
Kevin Bealer wrote:
    I could probably fix this if I knew how to use static if or "is" to find
    out whether something is a static array or not.

Phobos has an isStaticArray template in std.traits. Nice work btw.
Jan 22 2007
parent Kevin Bealer <kevinbealer gmail.com> writes:
== Quote from Lutger (lutger.blijdestijn gmail.com)'s article
 Kevin Bealer wrote:
    I could probably fix this if I knew how to use static if or "is" to find
    out whether something is a static array or not.

Nice work btw.

Thanks. I didn't end up using the static array test but I managed to get a solution using other tools from that module -- thanks for reminding me of it. Kevin
Jan 22 2007