www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - PROPOSAL: mixin candy

I'd like to propose an extension to templates:

I've given this a lot of thought, and carefully thought through each of the
points.  I hope I wrote this according to the NG conventions, and that it is
clear enough to be understood.  I'd like to know if this would be appropriate
for D.

Currently with template mixins, any declarations that are legal in a template
can be arbitrarily inserted into code.  If statements or expressions need to be
inserted in a function, the only option is to generate a string, and use
mixin().

I propose the following language changes to reduce the need to handle strings
when using mixins.

1. A mixin operator that can replace identifiers

Templates currently allow the creation of declarations using arguments to
decide the types, and values.  You cannot, however, choose the name
declarations depending on the arguments, without some difficult to read string
concatenation:
--------------------------------
template CreateVar(T, char[] name)
{
	mixin(T.stringof ~ " " ~ name ~ ";"); // compiles fine, but hard to read
	
}
--------------------------------

I think, for the sake of readability, and simplicity templates should have the
ability to name enclosed declarations using a single string argument.
template CreateVar(T, char[] name)
{
	T mixin(name); // easier to read, less likely to make a mistake
}

A more complex example:
--------------------------------
template Property(char[] propName, alias member, Type)
{
	Type mixin(propname)()
	{
		return mixin(member.stringof);
	}
	
	void mixin(propname)(Type val)
	{
		mixin(member.stringof) = val;
	}
}
--------------------------------

I'm not sure if this is so, but using mixin() might cause parsing ambiguities,
if it does, I propose a new token. For example, "~~(" and ")":

---------------------------------
template Property(char[] propName, alias member, Type)
{
	Type ~~(propname)()
	{
		return ~~(member.stringof);
	}
	
	void ~~(propname)(Type val)
	{
		~~(member.stringof) = val;
	}
}
-----------------------------------

This is easier to read, and the string concatenation operator seems
appropriate, since the operator is telling the compiler to insert the string's
value in it's place.

2. mixin declarations:

Template mixins only allow full declarations, the only way to add statements
within a function, or mixin an expression within a statement is to work with
strings that are passed to mixin():
--------------------------------
template MakeStructMember(alias structName, char[] structMember)
{
	const char[] MakeStructReturn = structName.stringof 
		~ "." ~ structMember ~ ";";
}

...
int getMember()
{
	return mixin(MakeStructMember!("thisStruct", "val"));
}
...
--------------------------------

With a mixin declaration, syntactically valid expressions and statements could
be written without the need to build strings.  Note that it uses a mixin to
replace the identifiers, like I describe above:

--------------------------------
mixin StructMember(alias structName, char[] structMember)
{
	structName. mixin(structMember)
}

...
int getMember(char[] structMember)()
{
	return mixin(StructMember!(structName, structMember));
}
...
--------------------------------

Notice the mixin can accept expressions instead of the need for complete
statements.  I think this can be done without complicating the parser, because
the contents of the mixin body need only have the same correctness a string has
when passed to mixin().  All keywords, names, and operators must already be
syntactically valid, with any names declared within the template handled as
string arguments to mixin().  The text within the blocks can be thought of by
the compiler as "data that needs to be stringized."

Combining incomplete statements with conditional compilation might pose
additional problems.  I think it can be solved, as long as incomplete
statements are placed inside "{" and "}" blocks.  Then the parser would would
treat the items within the static if blocks as "data that needs to be
stringized," like above.

A mixin instantiation would return a static string representing the code within
the brackets, after conditional compilation, substitution of arguments(same
rules as template arguments), and other mixin statements.  The compiler should
easily be able to convert them to a template:

---------------------------------
mixin StructMember(alias structName, char[] structMember)
{
	structName.mixin(structMember)
}
---------------------------------

is functionally equivalent to:

---------------------------------
template StructMember(alias structName, char[] structMember)
{
	const char[] StructMember = structName.stringof ~ "." ~ structMember;
}
---------------------------------

An example for an enumeration made with a tuple of strings, again using
identifier replacement:
---------------------------------
mixin EnumItemList(LIST...)
{
	static if(LIST.length > 1)	
	{
		mixin(LIST[0]),
		mixin(EnumItemList!(LIST[1..length])
	}
	else
	{
		mixin(LIST[0])
	}
}

template EnumDeclaration(char[] name, VALUES...)
{
	enum mixin(name)
	{
		mixin(EnumItemList!(VALUES))
	} 
}

---------------------------------

And the above converted to template definitions:
---------------------------------
template EnumItemList(LIST...)
{
	static if(LIST.length > 1)
	{
		const char[] EnumItemList = LIST[0] ~ "," ~ "\n" ~ 
			EnumItemList!(LIST[1..length]);
	}
	else
	{
		const char[] EnumItemList = LIST[0];
	}
}

template EnumDeclaration(char[] name, VALUES...)
{
	enum mixin(name)
	{
		mixin(EnumItemList!(VALUES))
	} 
}
---------------------------------

ryan
Mar 29 2008