Metaprogramming in D : Some Real-world Examples

Nick Sabalausky a at a.a
Mon Nov 9 18:46:00 PST 2009


"Bill Baxter" <wbaxter at gmail.com> wrote in message 
news:mailman.290.1257812868.20261.digitalmars-d-announce at puremagic.com...
> On Mon, Nov 9, 2009 at 4:09 PM, Walter Bright
> <newshound1 at digitalmars.com> wrote:
>> Looks like Bill Baxter is giving a presentation on D Nov. 18!
>>
>> http://www.nwcpp.org/
>
> Yep, that's right, and I'd be quite grateful to you smart folks here
> if you could share your meta-programming favorites with me!   If
> you've got a real-world example of meta-programming in D that you
> think is particularly handy, then please send it my way
>
> I'm looking for small-but-useful things that are easy to explain, and
> make something easier than it would be otherwise.  Things like places
> where static if can save your butt,  or loop unrolling,  and passing
> code snippets to functions like in std.algorithm.
>
> Things like a compile-time raytracer or regexp parser (though quite
> cool!) are not what I'm after.  Too involved for a short talk.
>
> --bb

I have a few things, mostly part of my SemiTwist D Tools project 
(http://www.dsource.org/projects/semitwist/):

=== Trace / Trace Value ===

Two of my personal favorites (particularly traceVal). Great for debugging.

Implementation and Documentation (search for "trace" and "traceVal", 
implementations are less than 10 lines each):
http://www.dsource.org/projects/semitwist/browser/trunk/src/semitwist/util/mixins.d

Quick Example:
-----------------------------
mixin(trace!());
mixin(trace!("--EASY TO VISUALLY GREP--"));

int myVar=100;
mixin(traceVal!("myVar  "));
mixin(traceVal!("myVar-1"));
mixin(traceVal!("min(4,7)", "max(4,7)"));
-----------------------------

Output:
-----------------------------
C:\path\file.d(1): trace
--EASY TO VISUALLY GREP--: C:\path\file.d(3): trace
myVar  : 100
myVar-1: 99
min(4,7): 4
max(4,7): 7
-----------------------------

There's also a traceMixin that can be useful for debugging mixins (it'll 
replace a normal string mixin and echo at compile time via "pragma(msg, )" 
the string being mixed in).

=== Init Member ===

Great for DRY in constructors with many initialization parameters.

Implementation and Documentation (at the top of the file):
http://www.dsource.org/projects/semitwist/browser/trunk/src/semitwist/util/mixins.d

Quick Example:
-----------------------------
mixin(initMember!(someVar));
mixin(initMember!(a, b, c));
-----------------------------

Turns Into:
-----------------------------
this.someVar = someVar;
this.a = a;
this.b = b;
this.c = c;
-----------------------------

Some variations are also available, such as initMemberFrom for copy 
constructors and initFrom copying class members to local vars.

=== Getters ===

DRY mixins for publicly read-only properties, and a "lazy" version for 
lazily computed & cached read-only properties. A poor replacement for a real 
DRY property syntax, but the next best thing.

Implementation and Documentation (search for "getter" and "getterLazy"):
http://www.dsource.org/projects/semitwist/browser/trunk/src/semitwist/util/mixins.d

Quick Example:
-----------------------------
// Third param optional
mixin(getter!(float, "someFloat", 2.5));

mixin(getterLazy!(int, "myVar"));
private int _myVar_gen()
{
    // Ordinarily, this function would be much more complex
    // Also, any member func can set "_myVar_cached = false;"
    // to force this to be re-computed on the next request.
    return 7;
}
-----------------------------


Turns Into:
-----------------------------
private float _someFloat = 2.5;
private float someFloat(float _NEW_VAL_)
{
    _someFloat = _NEW_VAL_;
    return _someFloat;
}
public float someFloat()
{
    return _someFloat;
}

private int _myVar;
private bool _myVar_cached = false;
public int myVar() {
    if(!_myVar_cached) {
        _myVar_cached = true;
        _myVar = _myVar_gen();
    }
    return _myVar;
}
private int _myVar_gen()
{
    // Ordinarily, this function would be much more complex
    // Also, any member func can set "_myVar_cached = false;"
    // to force this to be re-computed on the next request.
    return 7;
}
-----------------------------

Variations are also available to use "protected" (or anything else) instead 
of private.

=== Defer Assert/Ensure ===

Uses metaprogramming to create an alternative to assert() that provides much 
of the usefulness of JUnit-style libs, but without the bulk of wrapping 
things like '==' and '||' in classes/structs or forcing unnatural syntax 
like '(a.equals(b)).or(c.notEquals(d))'. Also reports unexpected exceptions 
and allows requiring a particular exception to be thrown. Implemented in 
less than 200 lines, including blank lines and comments.

Implementation:
http://www.dsource.org/projects/semitwist/browser/trunk/src/semitwist/util/deferAssert.d

Simple Test App:
http://www.dsource.org/projects/semitwist/browser/trunk/src/semitwist/apps/tests/deferAssertTest/main.d

Quick Example:
-----------------------------
int foo = 2;
mixin(deferAssert!(`foo == 3 || foo > 5`, "foo is bad"));
mixin(deferEnsure!(`foo`, `_ == 3 || _ > 5`, "ensure foo failed"));
flushAsserts();
-----------------------------

Output:
-----------------------------
src\semitwist\apps\tests\deferAssertTest\main.d(37): Assert Failed (foo == 3 
|| foo > 5): foo is bad
src\semitwist\apps\tests\deferAssertTest\main.d(42): Ensure Failed: ensure 
foo failed
Expression 'foo':
Expected: _ == 3 || _ > 5
Actual: 2
tango.core.Exception.AssertException at src\semitwist\util\deferAssert.d(170): 
2 Assert Failures
-----------------------------

=== Compile-time checking on types with non-identifier strings ===

Another of my favorites. This is from an upcoming release of my Goldie ( 
http://www.dsource.org/projects/goldie ) parser library.

I have a class to represent a token, which could have any name like "+" or 
"Statement", etc., all depending on the user-defined grammar. Currently, 
this token name representation is dynamic/run-time, which is very flexible, 
but there's no compile-time checking of these names, which often a bad 
tradeoff because many programs that need to parse something already know (in 
fact, *must* already know) the token names, so the flexibility doesn't help 
them at all, but compile-time checking would.

Unfortunately, the traditional approach to adding static checking would 
require every token name to be a valid identifier name, so for instance, "+" 
would have to be changed to "Plus", etc. And this can be problematic. It's 
extra work. It's very difficult to automate while getting good well-named 
results. It's possible (in a company setting, for instance) that the 
programmer might not be allowed or able to change the grammar. And of 
course, there's the possibility for name collisions (ie, what if "Plus" was 
already taken by another token?). Also, the new static class definition 
would probably need to be duplicated for each token. But all those problems 
can be solved in D quite easily with just a very small amount of some very 
simple metaprogramming.

The actual implementation Goldie uses does get fairly involved, but the 
basic concept is dead simple. All it takes is one templated type paramater, 
one static if and one static assert (and the static if could easily be 
eliminated):

-------------------------------------------------
// ----------- Dynamic Token -----------

// A Dynamic token. Flexible, but no compile-time checking
class DynamicToken
{
    protected char[] name;
    this(char[] name)
    {
        if(name != "+" && name != "Statement")
            throw new Exception("Invalid token: " ~ name);

        this.name = name;
    }
    // More Tokeny stuff here
}
unittest
{
    auto t1 = new DynamicToken("+");
    auto t2 = new DynamicToken("Statement");
    auto t3 = new DynamicToken("Stment"); // Runtime error
}

// ----------- Old-Style Static Token -----------

class OldStyle_StaticToken_Statement : DynamicToken
{
    this() { super("Statement"); }
}
// Note that "+" must be changed to "Plus",
// Also, the whole class definition has to be repeated
// (Although it's pretty small in this case.)
class OldStyle_StaticToken_Plus : DynamicToken
{
    this() { super("+"); }
}
unittest
{
    // Supposed to be +, but had to be changed to Plus
    // by either the grammar author or by special complex
    // logic in the StaticToken-generation tool.
    // And if there was already another token actually named "Plus"...
    // Well, even more complexity arises.

    auto t1 = new OldStyle_StaticToken_Plus;
    auto t2 = new OldStyle_StaticToken_Statement;
    //auto t3 = new OldStyle_StaticToken_Stment; // Compile-time error
}

// ----------- New-Style Static Token -----------

class NewStyle_StaticToken(char[] staticName) : DynamicToken
{
    static if(staticName != "+" && staticName != "Statement")
        static assert(false, "Invalid token: " ~ staticName);

    this() { super(staticName); }
}
unittest
{
    auto t1 = new NewStyle_StaticToken!("+");
    auto t2 = new NewStyle_StaticToken!("Statement");
    //auto t3 = new NewStyle_StaticToken!("Stment"); // Compile-time error
}
-------------------------------------------------




More information about the Digitalmars-d-announce mailing list