Compile time function execution...
Kevin Bealer
kevinbealer at gmail.com
Fri Feb 16 00:50:46 PST 2007
Walter Bright wrote:
> Andrei Alexandrescu (See Website For Email) wrote:
>> This is by far the least interesting application of this stuff. I
>> don't even count it when I think of the feature. "Oh, yeah, I could
>> compile square root at compile time. How quaint."
>
> I agree. I need a better example. Any ideas?
(Sorry that this got so long -- it kind of turned into a duffel bag of
things I've been thinking about.)
I think the most common case is like the plot for a TV show. Code that
is semi-interesting, semi-predictable, and semi-repetitive. If the code
is too interesting, you would need to write it all. If it was too
repetitive, you could just use a standard D template (boilerplate).
Tasks that are in between -- non-boilerplate, but fairly 'formulaic',
where the details are not interesting, is the candidate for this.
To me there are a couple special reasons that stick out.
1. You're building complex types and need to base them on standard
definitions that might change. I see this with ASN.1 definitions
at work. We have a program that builds C++ code from ASN.1 or
XML schemas.
A. Some of our programs need to stream 100s of MB of data so this
code needs to be as fast as possible.
B. If a field is added to the definition it has to appear in all
the code objects.
C. There is additional logic -- i.e. if a 'mandatory' field is
not assigned, the serializer has to throw an exception.
2. You're building the inner loop in some performance critical
application and the rules (expressions and conditional logic)
used there need or benefit from ultra-optimization.
This is what (in my view) compile time regex is for, I would
normally use a runtime regex for ordinary things like parsing
configuration files.
3. You need a lot of code duplication (i.e. to provide stub functions
something) and don't want to repeat yourself to get it.
---
This is how I imagine it:
Some of these ideas have been kicking around in my head, but I'm not
sure how practical they are. When I use the word templates here but I
mean any kind of code generation.
Starting scenario: Let's say I'm writing a program to solve some
mathematical task.
1. I create a top level class and add some members to it.
2. I add some sub-classes, a dozen or so, mostly just POD stuff.
3. Some of these have associative arrays, user-defined tree stuff,
regular arrays, hand coded linked lists, etc.
4. I put in a bunch of graph theory code and number crunching stuff.
Uses of metaprogramming:
1. Now let's say that this application is taking a while to run, so I
decide to run it in steps and checkpoint the results to disk.
- I write a simple template that can take an arbitrary class and
write the pointer value and the class's data to disk. (Actual data
is just strings and integers, so one template should cover all of
these classes.)
- For each internal container it can run over the members and do
the same to every object with a distinct memory address. (one
more template is needed for each container concept, like AA,
list or array -- say 4 or 5 more templates. It only writes each
object once by tracking pointers in a set.
- Another template that can read this stuff back in, and fix the
pointers so that they link up correctly.
(** All of this is much easier than normal, because I can generate
types using typelists as a starting point. I think in C++ this is
a bit trickly because it convolutes the structure definition --
recursively nested structs and all that; but with code generation, the
"struct builder" can take a list of strings and pump out a struct whose
definition looks exactly like a hand coded struct would look, but
maybe with more utilitarian functionality since its cheaper to add
the automated stuff. **)
Three or four templates later, I have a system for checkpointing any
data structure (with a few exceptions like sockets etc.), to a
string or stream.
2. I want to display this stuff to the user.
I bang together another couple of templates that can show these kinds
of code objects in a simple viewer. It works just like the last one,
finding variable names and values and doing the writefln() types of
tricks to give the user the details. Some kind of browser lets me
examine the process starting at the top. Maybe it looks a little like
a flow chart and a little like a debugger's print of a structure.
- I can define hooks in the important kinds of objects so they can
override their own displays but simple data can work without much
help.
3. I want to build a distributed compute farm for this numerical task.
- I just need to change the serialization to stream the data objects
over the web or sockets, or queue the objects in SQL tables. Some load
balancing, etc. Another application that has the same class definitions
can pull in the XML or ASN.1 or home-made serialization format.
The trick here is that we need to be able to build templates that can
inspect the objects and trees of objects in complex ways -- does this
class contain a field named "password"; is this other field a computed
value that can be thrown away. Does this other class override a method
named 'optimizeForTransport'.
Adding arbitrary attributes and arbitrary bits of code and annotation to
the classes is not too hard to do, because my original code generation
functions used typelists and had hooks for specifying special behavior.
4. I decide to allow my ten closest friends to help with the application
by rewriting important subroutines.
- Each person adds code for the application to an SQL database. A
simple script can now pull code from the database and dump it to text
files. This code can be imported into classes and run.
- I can generate ten different versions of a critical loop and select
which one to run at random. The timing output results is stored in a
text file. Later compiles of the code do "arc-profiling" of entire
algorithms or modules.
Kevin
More information about the Digitalmars-d
mailing list