Welcome to This Week in D! Each week, we'll summarize what's been going on in the D community and write brief advice columns to help you get the most out of the D Programming Language.
The D Programming Language is a general purpose programming language that offers modern convenience, modeling power, and native efficiency with a familiar C-style syntax.
This Week in D has an RSS feed.
This Week in D is edited by Adam D. Ruppe. Contact me with any questions, comments, or contributions.
See more at the announce forum.
Following up on last week, I want to talk about minimizing string mixins.
I've noticed a trend among D programmers: we have a bad habit of reaching for string mixins (and, perhaps, other generic tools..) when we don't strictly need them. It is true that string mixins can do just about anything, but as we learned in Star Trek VI, let us redefine progress to be that just because we can do a thing, it does not necessarily follow that we must do that thing.
String mixins can quickly become difficult to read, difficult to debug, and fickle to get right. (That said, there are techniques to make it better, including several tips from previous issues of This Week in D!) You'll want to keep them short and avoid them when you don't need them.
I like to keep my string mixins to about one line long. There might be several one-line mixins to make a declaration, but if I can at all avoid it, I want to keep each piece short and independent. There's a few tricks I use:
Let's look at a realistic, though very simplified, example. Suppose we want to read a SQL create table statement and generate a magic D object out of it:
enum sql = ` CREATE TABLE Person ( id INTEGER NOT NULL, name TEXT, birthday DATE ); `; /+ We want to do a transformation to generate something like this: class Person { int id; string text; Date birthday; void save() { /* implementation elided */ } } +/
Your first idea might be to build the whole class as a string and mix it all in. But that would include potentially long method implementations that are difficult to debug in strings. I'd avoid that:
There are three parts generated from that code: the name of the class, the types of the variables, and the names of the variables. The name of the class is a tricky one: since mixins must be a complete declaration, you cannot just write
class mixin(name_string) { }
The compiler will complain that name_string is nonsense in that context; you'll have parse errors. As we learned last week, mixin is NOT a code paster. But, you CAN write a template class with an internal name, and expose it via an alias:
class ObjectImpl(some_arguments) { // implementation } mixin("alias " ~ some_name ~ " ObjectImpl!(whatever, args);");
The end user can now pretend it has the pretty name, while you can write the code outside the string mixin. The only real leak of the internal name will be in compiler error messages, so don't make the internal name *too* ugly, but if you do it right, you can avoid many of those error messages.
Enough overview. Let's write some code.
enum sql = ` CREATE TABLE Person ( id INTEGER NOT NULL, name TEXT, birthday DATE ); `; /* This isn't a real sql parser, but it is a basic idea good enough for the example. When reading a string, it is best to write some kind of parser to get structured data. Work with strings only when you must. */ struct SqlColumn { string name; string type; } struct SqlTable { string name; SqlColumn[] columns; } SqlTable parseSqlTableStatement(string sql) { SqlTable table; // implementation elided, this post // isn't about writing string processing // code :) table.name = "Person"; table.columns = [ SqlColumn("id", "INTEGER"), SqlColumn("name", "TEXT"), SqlColumn("birthday", "DATE"), ]; return table; } // This is the fancy object. We could generate this class as // a gigantic string, but instead, I'm going to break it up. // First, a mixin template holds all the declarations and takes // the string. mixin template SqlTableObject(string sql) { // Converting the SQL type strings to a D type // will be done with a template that gives a // native D type via a static if list rather // than a string generator. template SqlVariableType(string sqlType) { static if(sqlType == "INTEGER") alias SqlVariableType = int; else static if(sqlType == "DATE") { import std.datetime; alias SqlVariableType = Date; } else static if(sqlType == "TEXT") alias SqlVariableType = string; else static assert(0, "unknown type " ~ sqlType); } // this is a uda for reflection struct sqlcolumn { string name; } // This is the part that generates the variables. It takes // the parsed object and generates the variables just one // at a time. Some string mixin is used, but I keep it small. mixin template SqlVariables(SqlColumn[] columns) { mixin(" // this UDA will be used for reflection later @sqlcolumn(columns[0].name) // Notice that the type translation is INSIDE the string. // Use of .stringof tends to be a mistake in string mixin // code! Use the actual names in the proper scope for best // effect. Observe that the Date type will work here despite // the import being local to SqlVariableType thanks to this // technique. Indeed, thanks to the recursion, we don't even // need to generate code for the index! Readable code with // just one trivial concatenation. SqlVariableType!(columns[0].type) " ~ columns[0].name ~ ";"); // There is no static foreach to loop over the array in a // declaration context. We might be tempted to do a helper // function that generates a string via a normal runtime // foreach statement. But that string can get messy fast. // // As an alternative, I want to use static if with recursion, // to get closer to the original static foreach idea. static if(columns.length > 1) mixin SqlVariables!(columns[1 .. $]); } // Now, the object is just a regular declaration, albeit with // a mixin for the variables. class OurSqlTableObject { // here's the variables being added! mixin SqlVariables!(parseSqlTableStatement(sql).columns); void save() { import std.stdio; import std.traits; // and this uses reflection to look at what was generated foreach(idx, variable; getSymbolsByUDA!(OurSqlTableObject, sqlcolumn)) { auto name = getSymbolsByUDA!(OurSqlTableObject, sqlcolumn)[idx].stringof; writeln(name, " = ", variable); } } } // Finally, it uses the alias - again in a string mixin - to give it a prettier // name on the outside. mixin("alias " ~ parseSqlTableStatement(sql).name ~ " = OurSqlTableObject;"); } // Now, by mixing in the whole thing, the magic is done easily for the end user. mixin SqlTableObject!(sql); void main() { auto person = new Person; person.id = 10; person.save(); }
A job that may have been a hundred lines of messy string concatenation mixin code is now done with mostly traditional techniques and only a couple lines of string mixin. The result is more traditionally written code, which is hopefully easier to read, easier to write, and easier to understand.
String mixins are a great do-it-all tool, but we can avoid them in most cases. I try to use them to just fill the small gaps between other language features.
To learn more about D and what's happening in D: