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.

Reply via email to