digitalmars.D.announce - Futurism lib (futures in D)
- Kevin Bealer (16/16) Jan 21 2007 I posted my future implementation dsource.org. It's called "Futurism". ...
- Kevin Bealer (11/28) Jan 21 2007 It took me a bit longer than I expected, when I accidentally did this:
- Saaa (4/4) Jan 21 2007 I read wikipedia about future and it sounds interesting.
- Saaa (2/2) Jan 21 2007 :)
- Kevin Bealer (58/62) Jan 21 2007 Let's say I needed to find the sum of an array.
- Mikola Lysenko (10/15) Jan 21 2007 Futures are a powerful technique to increase the parallelism of an
- Kevin Bealer (16/31) Jan 22 2007 I see some truth in this. I also think the dining philosopher problem
- Daniel Keep (14/40) Jan 21 2007 Looks good; more sophisticated than my own. A few initial thoughts:
- Kevin Bealer (20/34) Jan 21 2007 I wanted the first checkin to be "stealing free" ;), but I was actually ...
-
Kristian Kilpi
(20/31)
Jan 23 2007
On Mon, 22 Jan 2007 03:19:53 +0200, Kevin Bealer
... - =?ISO-8859-1?Q?Jari-Matti_M=E4kel=E4?= (5/7) Jan 23 2007 std.cpuid has threadsPerCPU(). See
- Kevin Bealer (13/20) Jan 24 2007 My tendency would be for each program to use enough threads to utilize a...
- =?ISO-8859-1?Q?Jari-Matti_M=E4kel=E4?= (8/27) Jan 24 2007 Yeah, this is probably the best for desktop use, where the machine is
- Kevin Bealer (17/31) Jan 24 2007 I added something like this to Futurism; it uses five criteria to pick t...
- Walter Bright (3/5) Jan 26 2007 Multithreaded code can speed up computation even on a machine with only
- Frits van Bommel (4/10) Jan 26 2007 So? You may still want twice as many threads if you've got twice the
- Walter Bright (2/13) Jan 26 2007 Ok.
- Kevin Bealer (17/45) Jan 22 2007 By the way, I've done this part -- I wrote a function called make_future...
- Daniel Keep (25/55) Jan 22 2007 I tried assigning directly, and it didn't like that, hence the foreach
- Kevin Bealer (11/47) Jan 22 2007 I went through the same thing - but probably quicker because I could loo...
- Kevin Bealer (26/48) Jan 22 2007 I didn't fully appreciate this statement until I got the solution workin...
- Daniel Keep (8/53) Jan 23 2007 I know the feeling. There's nothing quite like the moment when
- Lutger (3/5) Jan 22 2007 Phobos has an isStaticArray template in std.traits.
- Kevin Bealer (4/9) Jan 22 2007 Thanks. I didn't end up using the static array test but I managed to ge...
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
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
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
:) http://www.dsource.org/projects/futurism/browser/trunk/futurism/readme.txt
Jan 21 2007
== Quote from Saaa (empty needmail.com)'s articleI 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
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
== Quote from Mikola Lysenko (mclysenk mtu.edu)'s articleSaaa wrote: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 BealerAs 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 22 2007
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
== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s articleKevin 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 :) -- DanielThanks - I appreciate the suggestions, too. Kevin
Jan 21 2007
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=he =number of hardware threads, but I think its a matter of tuning how many more tha=n =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 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
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
== Quote from Jari-Matti_Mäkelä (jmjmak utu.fi.invalid)'s articleKristian Kilpi kirjoitti: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.) KevinBTW, 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 24 2007
Kevin Bealer kirjoitti:== Quote from Jari-Matti_Mäkelä (jmjmak utu.fi.invalid)'s articleYeah, 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.Kristian Kilpi kirjoitti: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.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?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
== Quote from Kristian Kilpi (kjkilpi gmail.com)'s articleOn 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 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 because the library is designed so that the main thread pulls some of the weight. Kevin
Jan 24 2007
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
Walter Bright wrote:Kristian Kilpi wrote: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.(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
Frits van Bommel wrote:Walter Bright wrote:Ok.Kristian Kilpi wrote: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.(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
== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s articleKevin Bealer wrote:...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. Kevinint 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.
Jan 22 2007
Kevin Bealer wrote:== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s articleP.S. Of course I don't mind; I'm happy to be able to contribute!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.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. KevinAh 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
== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s articleKevin 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.I'll look into this angle. I imagine it will require some type-list style tuple recursion.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. KevinAh 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 :PI'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!" -- BenderOh 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
== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s articleKevin Bealer wrote:...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. Kevin2. 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. KevinOf 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.
Jan 22 2007
Kevin Bealer wrote:== Quote from Daniel Keep (daniel.keep+lists gmail.com)'s articleI know the feeling. There's nothing quite like the moment when something new finally clicks into place.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); }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. KevinThat, and our ability to abuse^Hmake use of some of D's advanced features :) -- Daniel
Jan 23 2007
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
== Quote from Lutger (lutger.blijdestijn gmail.com)'s articleKevin Bealer wrote: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. KevinI 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