foreach and metaprogramming

Kyle Furlong kylefurlong at gmail.com
Wed Nov 8 13:10:37 PST 2006


Per Eckerdal wrote:
>> I'm from the speed is good camp. I like D because it seems to give 
>> good speed
>> while still being easy to use.
> 
> (Sorry for the long post.) I don't think speed needs to be a problem if 
> you implement it as a compile-time language for lexical-aware macros. 
> Here is one potential way of doing this, to show you what I'm thinking 
> about:
> 
> This would consist of four different language features.
> 
> The first one is just syntactic sugar and should be fairly easy to 
> implement: Ruby-style blocks. It could be implemented as just sending a 
> delegate as the last function argument:
> 
> foo(1) { writefln("Hello"); }
> foo(1, { writefln("Hello"); });
> 
> Would be the same. I don't know if this is hard to do, but I can't see 
> why it would. And it makes for some very elegant things at other places 
> as well, look at Ruby for examples.
> 
> The second one is two new operators, I'll call them $ and #. $ and # can 
> be put before expressions, statements, declarations and identifiers. 
> Examples are: $int a = 5; ${ /* code block */ } class $Name {}. It binds 
> very tightly, you should be able to assume that only the thing next to 
> it is bound.
> 
> # returns the AST of the following expression, $ is for declaring macro 
> blocks.
> 
> $ blocks may return nothing, and then they will be replaced by nothing: 
> ${int a = 5;}; gives nothing. They can also return literals, e.g. int a 
> = ${return 5;}; Or, they can return AST objects, and then they will be 
> replaced by that tree:
> 
> ${ return #{if (a == 5) die();}; } // This is equal to if (a == 5) die();
> 
> The argument to # must be a complete expression/declaration/statement, 
> so #{ if (a == } is invalid. The only operation you can do with ASTs 
> created with # is to concatenate with the ~ operator.
> 
> I think you could implement this by making all $ blocks functions in a 
> separate "meta"-module (used only internally when compiling). So if you 
> define a function within a macro block, you define a function in the 
> "meta"-module, essentially defining a macro that can be invoked with
> 
> ${return macroname(args);}
> 
> Given this, most of the features of this can be done, but I'd suggest 
> adding some more syntax sugar, a macro keyword:
> 
> macro foreach;
> 
> This declares that all foreach calls should be surrounded within 
> ${return ;} and all arguments enclosed in #{}. So you will be able to 
> access the compile-time function foreach as though it was a normal 
> function. There are one more thing to add:
> 
> macro int hello(int a) {
>   return a;
> }
> 
> is equivalent to
> ${ int hello(int a) { return a; } };
> macro hello;
> 
> The last feature that might be added is a library that only compile-time 
> code can access that allows you to reflect over classes and functions. 
> (Probably only in the same module as the calling code, but I don't think 
> that's a big limitation)
> 
> As an example I will show how you can implement a nifty kind of 
> iterators using this technique.
> 
> class LinkedList {
>   // ...
>   macro each(void delegate(Type t) block) {
>     return #{
>       while (iterate over the list) {
>          block(data);
>       }
>     };
>   }
>   // ...
> }
> 
> LinkedList ll = new LinkedList;
> // Add data..
> // I borrowed Ruby's syntax. I don't really like it but it works as demo
> ll.each { |Type data|
>   writefln(data);
> }
> 
> (This example obviously requires the macro stuff to be done *after* 
> templates, but I think that makes sense.)
> 
> It can't be hard for the compiler to recognize inline delegate calls, so 
> this code should be able to get expanded into no method calls at all.
> 
> To sum this all up:
> 
> What I'm talking about is really only a slightly more hygienic and a lot 
> more powerful macro system. I can't see why this would reduce 
> performance, but I can see how this can be used to perform heavy 
> high-level optimization (for example when you have a tree that doesn't 
> map very well to an index operator or C++-iterators). And it would let 
> you do lots of other cool things as well, like automated persistence 
> layers without another DSL and things like that.
> 
> The great drawback I can see is that this would give much longer 
> compilation times. But that's the cost of doing more at compile time and 
> less at runtime, isn't it? (And you should be able to optimize it some, 
> as well)
> 
> One thing that is good about this is that you expose very few compiler 
> internals; only create and append AST is needed, and the rest can be 
> implemented as a normal (compile-time) API.
> 
> /Per

I agree with the sentiment of making compile-time programming more 
powerful. I'm not sure the syntax you introduce is what we are looking 
for, however.



More information about the Digitalmars-d mailing list