another idea for compile time functions

Kevin Bealer kevinbealer at gmail.com
Thu Feb 8 23:37:01 PST 2007


Andrei Alexandrescu (See Website For Email) wrote:
> janderson wrote:
>> Andrei Alexandrescu (See Website For Email) wrote:
>>> Hasan Aljudy wrote:
>>> [snip]
>>>> The trick may lie in letting the compiler recognize these kind of 
>>>> functions. A simple solution might me to come up with a new 
>>>> attribute; let's call it "meta":
>>>> # meta int add( int x, int y ) { return x + y; }
>>>> This attribute will assist the compiler in recognizing that this 
>>>> function can be computed at compile time if it's given constant 
>>>> arguments.
>>>
>>> This is much in keep with my idea on how metaprogramming should be 
>>> done, with the little semantic nit that "meta" should be "dual" as 
>>> add has dual functionality.
>>>
>>> The attribute is not even needed if meta-code is flagged as such. My 
>>> thoughts currently gravitate around the idea of using mixin as an 
>>> escape into compile-time world. Anything that's printed to standard 
>>> output in compile-time world becomes code, e.g. (using your function):
>>>
>>> mixin
>>> {
>>>   writefln("int x = ", add(10, 20), ";");
>>> }
>>>
>>> is entirely equivalent to:
>>>
>>> int x = 30;
>>>
>>> No annotation is needed on add because mixin clarifies that it's 
>>> called during compilation. The compiler will complain if add cannot 
>>> be user dually.
>>>
>>> Using mixin and write as separation devices makes it very clear what 
>>> is to be done when; otherwise, it quickly becomes confusing what code 
>>> is meant to actually get evaluated eagerly, and what code is to 
>>> actually be "output" for compilation.
>>>
>>> The not-so-nice thing is that we get to manipulate numbers, strings, 
>>> and arrays, not trees like in LISP.
>>>
>>>
>>> Andrei
>>
>> Although I like this idea, I fear it will not work for anything hidden 
>> in a library (if you are using something that is moved to a .lib, your 
>> code will stop working).  Maybe that's ok.  Actually now I think about 
>> it, that would be safer, because even if the D compiler could put an 
>> "ok" signatures in a library, someone could create fake signatures.
> 
> Good point. This is reasonable. To execute code during compilation it's 
> reasonable to expect transparency. C++ commercial vendors did not really 
> suffer financial loss due to this requirement, and at any rate, OSS is 
> on the rise :o).
> 
>> It could get confusing if you don't know which functions will work and 
>> which won't.  Perhaps the compiler could help there (spit out a list 
>> of functions u can use or something).
> 
> Yes, that's a documentation issue. The nice thing is that a plethora of 
> really useful functions (e.g. string manipulation) can be written in D's 
> subset that can be interpreted. Pretty much the entire string library 
> will be meta-executable. No more need for the metastrings lib!
> 
>> I think the compiler would compile these commands on demand and cache 
>> them so it doesn't need to do them every time.  That would help a lot. 
>> It could even cache results so it only needs to compute them once.
>>
>> Anyways, there i think there is so much possibility with compile time 
>> coding.
> 
> Me too. Once compile-time interpretation (and mutation) makes it in, I 
> think there's no fear of efficiency loss anymore. Speed will be 
> comparable to that of any interpreted code, and there are entire 
> communities that don't have a problem with that.
> 
> The question is, what is the subset of D that can be interpreted? I'm 
> thinking:
> 
> * Basic data types (they will be stored as a dynamically-typed variant 
> anyway), except pointers to functions and delegates.
> 
> * Arrays and hashes
> 
> * Basic expressions (except 'is', 'delete' et al.)
> 
> * if, for, foreach, while, do, switch, continue, break, return
> 
> I'm constructing this list thinking what it takes to write basic data 
> manipulation functions. What did I forget?
> 
> 
> Andrei

Simple answer: the intersection of a simple interpreted language and D. 
  What can D do that a scripted language cannot?  Take that out.

Approaching it from another angle, imagine I have a parse tree and I'm 
writing code to interpret it... what do I want to leave out?

- inline asm
- exceptions
- synchronization and volatile and related
- calls to C (though this might prompt some rewriting of Phobos to avoid
   calling things like strlen() if they aren't builtins/intrinsics.

What I see as a bigger snag is processing order.  The whole D universe 
expects to be able to forward or backward reference anything regardless 
of how it is defined.

Let's say I have these two functions:

char[] foo(char[] a, char[] b) metaok;
char[] bar(char[] a, char[] b, char[] c) metaok;

The metaok keyword means "this can be run at compile time."

Suppose further that bar(a,b,c) calls foo(a,b) as a base case, and that 
both are generated via the "meta" mechanism.  No mutual recursion or 
anything though.

What order do you compile them in?  Obviously, foo then bar -- bar needs 
foo to be runable at compile time.

I love the idea of makeing all of std.strings compile-time-able, but the 
D compiler sees the code as a big array of unrelated functions right now 
(or it can if it chooses to.)

With this stuff, the compiler is thrown back into the shell or C world 
where every function definition and execution is an event in a history, 
and order is everything.

The alternative I prefer, is to collect all function definitions, sort 
out the dependency order in the compiler using graphs or what not, and 
work in that order.  To be consistent with D philosophy as I see it, the 
detection of cycles in this graph should be flagged like an ambiguous 
overload.

That should be okay, but when compiling across modules it might get 
somewhat harder...?

Kevin



More information about the Digitalmars-d mailing list