On Sunday, 20 August 2017 at 19:27:43 UTC, WhatMeWorry wrote:
It's stuff like this which makes me very frustrated. Or
depressed because it demonstrates just how poor a programmer I
am:
string printStatement(string message) {
return `writeln("` ~ message ~ `");`;
}
void main()
{
// Mixins are for mixing in generated code into the source
code.
// The mixed in code may be generated as a template instance
// or a string.
mixin(printStatement("hello world"));
mixin(`writeln(` ~ `Hello` ~ `);` );
mixin("writeln(`World`);");
}
Compiling gives me the errors:
Error: undefined identifier Hello
To me, `writeln(` ~ `Hello` ~ `);` is a valid D string? Okay,
maybe a
string expression but a string nevertheless.
So, am I giving mixin more magical powers than it possesses?
Should we say that mixin needs to be given a "fully pre-formed
D compilable" string?
Thanks. especially to let me vent.
It's not difficult, it's just new. It's not that you are a poor
programmer, but you simply have not learned how to think about
mixins correctly. Stop whining about it and focus that energy on
working with them.
String mixins are very simple. It takes any string and inserts it
as code in to the program directly as if you typed it by hand.
What makes them useful is that you can build strings a compile
time and so essentially introduce compile time code generation.
e.g.,
L324: mixin("Hello World")
becomes
L324: Hello World
and so
mixin(N)
gets inserted as
N, as if you typed it in directly. (this is the important part. N
isn't inserted but the contents of N as a string.
What this is good for, is say you want to generate code based off
stuff at compile time, e.g., a configuration file. You can
generate valid D code using strings that load the configuration
file at compile time and do what you want with it.
e.g.,
enum config = import(myconfigfile);
config now contains, as a string, the contents of myconfigfile AT
COMPILE TIME. Normally we think of config as being a run time
variable, but it is simply a compile time variable(well, it can't
vary, unfortunately, the compile time processing is not a fully
integrated compile time compiler.
enum configCode = process(config);
let process be a function that takes config, extracts the data
from it and bundles it all up in new D code.
mixin(configCode);
Now mixes in that code direct in to the source as if we typed it.
e.g.,
enum classes = import("classNames");
string code;
foreach(n; classes.split(","))
code ~= "class "~n~";\n";
// at this point code should be something like "class X;\nclass
Y;" etc, but it depends on the file.
mixin(code);
has the same effect if we typed
class X;
class Y;
But the difference is that we used a file to extract the class
names and a string mixin that inserted the code. This way we
don't have to manually change the class names in our D file, we
just change the classNames file, which is probably autogenerated
anyways.
String mixins come in very handy when you have D code that can be
"generalized" (parameterized).
It's sort of the place holder concept: You have a D string like
"
if (alpha_1 > 0) { Alpha1(); }
if (alpha_2 > 0) { Alpha2();}
if (alpha_3 > 0) { Alpha3();}
if (alpha_4 > 0) { Alpha4();}
"
...
Obviously if you can simplify all that code it would be nice,
well you can!
for(int i = 0; i < N; i++)
mixin("if (alpha_"~i~" > 0) { Alpha"~i~"();}");
this will mix N of those lines with the proper mapping. I only
have to make one change rather than N.
You have to think of them as D code generators. Of course, you
don't have to use them to generate code, but they are insert,
foremost, in D code and will be interpreted by the D compiler.
mixin("string X = \"mixin string X = \""mixin string X =
\"""mixin string X = .....);
is the same as
string X = \"mixin string X = \""mixin string X = \"""mixin
string X = ....."""";
and, if we used enums(compile time object) instead of strings(run
time object), we could do
mixin(X); and it would mix in the next layer, which would redfine
X each time.
It's not difficult, just requires a different way to think about
them, as does anything that is unfamiliar.