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