How to mixin finction name?
Adam D. Ruppe
destructionator at gmail.com
Sun Apr 14 17:33:51 UTC 2019
On Sunday, 14 April 2019 at 15:13:37 UTC, Johannes Loher wrote:
> At first I was very confused that this example even worked. Why
> does `ch` get expanded in the call to writeln? It is part of
> the mixed in string, so why does the string not simply include
> "writeln(ch, ...)" on every iteration?
This is one of the fruits of my rule in another thread: avoid
.stringof, which pretty easily expands to most concatenation in
mixins too. Function names are one of the few exceptions, you
have to do some string concat there, but almost everywhere else,
local names just used very simply are winners.
Let's think about what `static foreach` and `mixin` actually do.
static foreach basically copy/pastes the code, with the iteration
variable replaced by the other symbol *transformed into a
literal*. So, with the code here
---
enum letters = ['A', 'B', 'C'];
static foreach(ch; letters)
{
mixin(q{
void print}~ch~q{(int i) {
import std.stdio;
writeln(ch, " - ", i);
}
});
}
---
That will expand to:
---
/* first iteration */
mixin(q{ void print} ~ 'A' /* letters[0].toLiteral */ ~ q{(int
i) {
import std.stdio;
writeln('A' /* letters[0].toLiteral */, " - ", i);
}});
/* second iteration */
mixin(q{ void print} ~ 'B' /* letters[1].toLiteral */ ~ q{(int
i) {
import std.stdio;
writeln('B' /* letters[1].toLiteral */, " - ", i);
}});
/* snip third iteration, you get the idea */
---
So, the compiler takes the current item of iteration, transforms
it into some kind of literal representation - like if you
literally wrote `'A'` or `2` or `"foo"` in the source code -
doing whatever CTFE magic it needs to get to it - and then
replaces the local name with that everywhere it appears.
Being a literal, you can use it as much as you want in the body,
and it never changes! It isn't like a normal loop where a local
variable's body is being replaced, this is copy/pasting code with
a new literal in place of your placeholder.
To see this in action, try modifying the code to get a pointer to
that iteration variable:
static foreach(ch; letters)
{
mixin(q{
void print}~ch~q{(int i) {
immutable char* ptr = &ch;
}
});
}
In regular foreach, that would work, you are allowed to take the
address of a local variable. But here, notice the compiler gives
you *three* errors:
$ dmd bre
bre.d-mixin-7(10): Error: cannot modify constant 'A'
bre.d-mixin-7(10): Error: cannot modify constant 'B'
bre.d-mixin-7(10): Error: cannot modify constant 'C'
What error do we get when we write
immutable char* ptr = &'A';
somewhere in our code? You guessed it:
bre.d(16): Error: cannot modify constant 'A'
The compiler generated the same error because it generated the
same code!
Knowing this is how static foreach works, it also explains why:
static foreach(num; 0..2)
{
int a = num;
}
Gives the error:
bre.d(5): Error: declaration bre.main.a is already defined
Because the compiler tried to expand that by pasting code
back-to-back:
int a = 0;
int a = 1;
The num -> literal here was fine, but since it just pasted the
loop body, we end up with the same name used twice.
This is why so many people write:
static foreach(num; 0 .. 2) {{
int a = num;
}} // notice the double brace
Because then the outer brace groups the body... and the inner
brace becomes part of the body. Thus, that expands to:
{
int a = 0;
}
{
int a = 1;
}
The expanded {} introduces a new scope for each iteration, which
the compiler allows to group local variables without overlapping
their names. (You can write that by hand too, I like using it to
limit scope of temporaries.)
But I'm digressing a little bit.
Back to the original, since the inner code has literals, you can
now mixin with no worry at all.
> If you do not care about your code being compatible with
> -betterC, you can use std.format to make the code even more
> readable (at least in my opinion):
Indeed, that does look nicer, though since now the mixin body
only needs a single ugly concat - at the name - I don't hate just
having that.
The one thing I did in my example that you also want to do though
is to be sure the opening of the string appears on the same line
as mixin.
YES:
mixin("
// code
");
NO:
mixin(
" code "
);
Why? The compiler just adds numbers of \n seen in the string to
the line where the mixin keyword appears. In the first option,
they appear on the same line, so the errors will be reported on
the same line; a + b + 0 = perfect.
In the second option, there is a line in between, so now we have
a + b + 1 = slightly off error and assert message line numbers.
I wrote about this over on my blog too back in January:
http://dpldocs.info/this-week-in-d/Blog.Posted_2019_01_14.html#the-generated-client
that linked section specifically is about subtleties of static
foreach + mixin.
More information about the Digitalmars-d-learn
mailing list