DeRailed DSL (was Re: compile-time regex redux)
Andrei Alexandrescu (See Website For Email)
SeeWebsiteForEmail at erdani.org
Fri Feb 9 19:01:11 PST 2007
Bill Baxter wrote:
> Bill Baxter wrote:
>> Andrei Alexandrescu (See Website For Email) wrote:
>>> Tom S wrote:
>>
>>>> Therefore, I'd like to see a case when a compile-time DSL processing
>>>> is going to be really useful in the real world, as to provoke
>>>> further complication of the compiler and its usage patterns.
>>>
>>> I can't parse this sentence. Did you mean "as opposed to provoking"
>>> instead of "as to provoke"?
>>
>> I think he just meant he wants to see some real world examples that
>> justify the additional complexity that will be added to the compiler
>> and language.
>>
>>>> The other aspect I observed in the discussions following the new dmd
>>>> release, is that folks are suggesting writing full language parsers,
>>>> D preprocessors and various sort of operations on complex languages,
>>>> notably extended-D parsing... This may sound weird in my lips, but
>>>> that's clearly abuse. What these people really want is a way to
>>>> extend the language's syntax, pretty much as Nemerle does it. And
>>>> frankly, if D is going to be a more powerful language, built-in text
>>>> preprocessing won't cut it. Full fledged macro support and syntax
>>>> extension mechanics are something that we should look at.
>>>
>>> This is a misunderstanding. The syntax is not to be extended. It
>>> stays fixed, and that is arguably a good thing. The semantics become
>>> more flexible. For example, they will make it easy to write a matrix
>>> operation:
>>>
>>> A = (I - B) * C + D
>>>
>>> and generate highly performant code from it. (There are many reasons
>>> for which that's way harder than it looks.)
>>
>> This is one thing I haven't really understood in the discussion. How
>> do the current proposals help that case? From what I'm getting you're
>> going to have to write every statement like the above as something like:
>>
>> mixin {
>> ProcessMatrixExpr!( "A = (I - B) * C + D;" );
>> }
>>
>> How do you get from this mixin/string processing stuff to an API that
>> I might actually be willing to use for common operations?
>>
>>
>>> I think there is a lot of apprehension and misunderstanding
>>> surrounding what metacode is able and supposed to do or simplify.
>>> Please, let's focus on understanding _before_ forming an opinion.
>>
>> I think that's exactly what Tom's getting at. He's asking for
>> examples of how this would make like better for him and others. I
>> think given your background you take it for granted that
>> metaprogramming is the future. But D is attracts folks from all kinds
>> of walks of life, because it promises to be a kinder, gentler C++. So
>> some people here aren't even sure why D needs templates at all.
>> Fortran doesn't have 'em after all. And Java just barely does.
>>
>> Anyway, I think it would help get everyone on board if some specific
>> and useful examples were given of how this solves real problems (and
>> no I don't really consider black and white holes as solving real
>> problems. I couldn't even find any non-astronomical uses of the terms
>> in a google search.)
>>
>> For instance, it would be nice to see some more concrete discussion about
>> * the "rails" case.
>> * the X = A*B + C matrix/vector expressions case.
>> * the case of generating bindings to scripting langauges / ORB stubs
>> * the Spirit/parser generator case
>>
>> So here's my take on vector expressions since that's the only one I
>> know anything about.
>>
>> *Problem statement*:
>> Make the expression A=(I-B)*C+D efficient, where the variables are
>> large vectors (I'll leave out matrices for now).
>>
>> *Why it's hard*:
>> The difficulty is that (ignoring SSE instruction etc) the most
>> efficient way to compute that is do all operations component-wise. So
>> instead of computing I-B then multiplying by C, you compute
>> A[i] = (I[i]-B[i])*C[i]+D[i];
>> for each i. This eliminates the need to allocate large intermediate
>> vectors.
>>
>> *Existing solutions*:
>> Expression templates in C++, e.g. The Blitz++ library. Instead of
>> making opSub in I-B return a new Vector object, you make opSub return
>> an ExpressionTemplate object. This is a little template struct that
>> contains a reference to I and to B, and knows how to subtract the two
>> in a component-wise manner. The types of I and B are template
>> parameters, LeftT and RightT. Its interface also allows it to be
>> treated just like a Vector. You can add a vector to it, subtract a
>> Vector from it etc.
>>
>> Now we go and try to multiply that result times C. The result of that
>> is a new MultExpressionTemplate with two paramters, the LeftT being
>> our previous SubExpressionTemplate!(Vector,Vector) and the RightT
>> being Vector.
>>
>> Proceeding on in this way eventually the result of the math is of type:
>>
>> AddExpressionTemplate!(
>> MultExpressionTemplate!(
>> SubExpressionTemplate!(Vector,Vector),
>> Vector),
>> Vector)
>>
>> And you can see that we basically have a parse tree expressed as
>> nested templates. The final trick is that a Vector.opAssign that
>> takes an ExpressionTemplate is provided and that method calls a method
>> of the expression template to finally trigger the calculation, like
>> expr.eval(this). eval() has the top-level loop over the components of
>> the answer.
>>
>> *Why Existing solutions are insufficient*
>> For that all that effort to actually be useful the compiler has to be
>> pretty agressive about inlining everything so that in the end all the
>> temporary template structs and function calls go away and you're just
>> left with one eval call. It can be tricky to get the code into just
>> the right configuration so that the compiler will do the inlining.
>> And even then results will depend on the quality of the compiler. My
>> attempts using MSVC++ several years ago always came out to be slower
>> than the naive version, but with about 10x the amount of code. The
>> code is also pretty difficult to follow because of all those different
>> types of expression templates that have to be created.
>>
>> If you include matrices things are even trickier because there are
>> special cases like "A*B+a*C" that can be computed efficiently by a
>> single optimized routine. You'd like to recognize such cases and turn
>> them into single calls to that fast routine. You might also want to
>> recognize "A*B+C" as a special case of that with a==1.
>>
>> *How it could be improved*:
>> ??? this is what I'd like to see explained better.
>
> Ok, here's my high-level stab at how the new stuff could help.
> Instead of returning "expression templates" where the the parse tree is
> represented as a hierarchical type, just make opSub (I-B) return an
> ExpressionWrapper which is just a light wrapper around the string "I-B".
> Next the compiler gets to "ExpressionWrapper * C", have that just
> return another ExpressionWrapper that modifies the original string to be
> "(" ~ origstring ~ "*C"
> In the end you still have an opAssign overload that calls eval() on the
> expression, but now the expression has a (fully parenthesized) string
> representation of the expression: "((I-B)*C)+D". And eval just has to
> use the new compile-time string parsing tricks to decide how best to
> evaluate that expression. It's a pattern matching task, and thankfully
> now you have the whole pattern in one place, as opposed to the
> ExpressionTemplates, which have a hard time really getting a look at the
> whole picture.
You're on the right track!!! I was writing an answer, but I need to
leave so I have to interrupt it.
Thanks for lending a hand :o).
Andrei
More information about the Digitalmars-d
mailing list