Thread overview
How to mixin finction name?
Apr 14
Andrey
Apr 14
Andrey
April 14
Hi,
I want to do something like this:
> void main()
> {
>     enum letters = ['A', 'B', 'C'];
>     static foreach(ch; letter)
>     {
>      	  void mixin("print" ~ ch)(uint i)
>         {
>          	writeln(ch, " - ", i);
>         }
>     }
> 
>     printB(6);
> }

Create some function in loop and use it. But I don't know how to mixin names?

Output:
> onlineapp.d(59): Error: no identifier for declarator void
> onlineapp.d(59): Error: found i when expecting . following uint
> onlineapp.d(59): Error: found ) when expecting identifier following uint.
> onlineapp.d(60): Error: found { when expecting ,
> onlineapp.d(61): Error: found ; when expecting ,
> onlineapp.d(62): Error: expression expected, not }
> onlineapp.d(63): Error: found } when expecting ,
> onlineapp.d(65): Error: found ; when expecting ,
> onlineapp.d(66): Error: expression expected, not }
> onlineapp.d(67): Error: found End of File when expecting ,
> onlineapp.d(67): Error: found End of File when expecting )
> onlineapp.d(67): Error: found End of File when expecting ;
> onlineapp.d(67): Error: found End of File when expecting } following compound statement
> onlineapp.d(67): Error: found End of File when expecting } following compound statement
April 14
On Sunday, 14 April 2019 at 10:07:30 UTC, Andrey wrote:
> Create some function in loop and use it. But I don't know how to mixin names?


import std.stdio;
void main()
{
     enum letters = ['A', 'B', 'C'];
     static foreach(ch; letters)
     {
         mixin("void print" ~ ch ~ "(uint i) { writeln('" ~ ch ~ "', \" - \", i); }");
     }

     printA(1);
     printB(2);
     printC(3);
}
April 14
On Sunday, 14 April 2019 at 11:44:16 UTC, Boris Carvajal wrote:
> On Sunday, 14 April 2019 at 10:07:30 UTC, Andrey wrote:
>> Create some function in loop and use it. But I don't know how to mixin names?
>
>
> import std.stdio;
> void main()
> {
>      enum letters = ['A', 'B', 'C'];
>      static foreach(ch; letters)
>      {
>          mixin("void print" ~ ch ~ "(uint i) { writeln('" ~ ch ~ "', \" - \", i); }");
>      }
>
>      printA(1);
>      printB(2);
>      printC(3);
> }

I want to mixin only name - not the full function code.
April 14
On Sunday, 14 April 2019 at 12:00:38 UTC, Andrey wrote:
> On Sunday, 14 April 2019 at 11:44:16 UTC, Boris Carvajal wrote:
>> On Sunday, 14 April 2019 at 10:07:30 UTC, Andrey wrote:
> I want to mixin only name - not the full function code.

I think you can't do a partial statement in a mixin.
But this works by declaring pointers to functions and assigning a function literal to them.

import std.stdio;
void main()
{
    auto dg = (uint i){ writeln('a' , " - ", i); };
     enum letters = ['A', 'B', 'C'];
     static foreach(ch; letters)
     {
         mixin("void function(uint i) print" ~ ch ~ ";");
         dg = (uint i){ writeln(ch , " - ", i); };
         mixin("print" ~ ch ~ " = dg;");
     }

     printA(1);
     printB(2);
     printC(3);
}
April 14
On Sunday, 14 April 2019 at 12:00:38 UTC, Andrey wrote:
> I want to mixin only name - not the full function code.

You can't. Best you can do is write the function separately and then mixin an alias for it with the other name.

void main()
{
    enum letters = ['A', 'B', 'C'];

    // normal implementation, parameterized via template
    void printImplementation(char ch)(uint i) {
        import std.stdio;
        writeln(ch, " - ", i);
    }

    static foreach(ch; letters)
    {
        // mixin the name separately
        mixin("alias print" ~ ch ~ " = printImplementation!ch;");
    }

    printB(6);
}


Though, I'd point out the mixin code doesn't have to be too ugly. Consider this:

void main()
{
    enum letters = ['A', 'B', 'C'];

    static foreach(ch; letters)
    {
    	mixin(q{
		void print}~ch~q{(int i) {
		    import std.stdio;
		    writeln(ch, " - ", i);
		}
	});
    }

    printB(6);
}


Well, the name part is a big ugly, but the rest of it looks perfectly normal, and the compiler error message still give reasonable results.
April 14
Am 14.04.19 um 15:22 schrieb Adam D. Ruppe:
> [...]
> Though, I'd point out the mixin code doesn't have to be too ugly.
> Consider this:
> 
> void main()
> {
>     enum letters = ['A', 'B', 'C'];
> 
>     static foreach(ch; letters)
>     {
>         mixin(q{
>         void print}~ch~q{(int i) {
>             import std.stdio;
>             writeln(ch, " - ", i);
>         }
>     });
>     }
> 
>     printB(6);
> }
> 
> 
> Well, the name part is a big ugly, but the rest of it looks perfectly normal, and the compiler error message still give reasonable results.
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?

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):

void main()
{
    import std.format : format;

    enum letters = ['A', 'B', 'C'];

    static foreach (ch; letters)
    {
        mixin(q{
            void print%s(int i) {
                import std.stdio;
                writeln(ch, " - ", i);
            }
        }.format(ch));
    }

    printB(6);
}
April 14
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.
April 14
On Sunday, 14 April 2019 at 17:33:51 UTC, Adam D. Ruppe wrote:
> That will expand to:

I'm sorry, I skipped a step here. The compiler doesn't look into the mixin string until after it calls mixin, so technically it goes:

static foreach ->
  mixin("void print" ~ 'A' ~ "(int a) {
       writeln(ch, " - ", a);
  }

And then, from there, mixin parses it and links in as if you wrote:

void printA(int a) {
   writeln(ch, " - ", a);
}

and then, since it is still in static foreach, the compiler will do another layer of transforming that `ch` placeholder and we end up with the final generated code:

void printA(int a) {
   writeln('A', " - -", a);
}



In the last post, I skipped that middle step. It doesn't change anything else, but still it is sometimes helpful to understand exactly what is actually happening.

But yeah, same result in the end - `ch` becomes the literal representation of the item being looped over, so 'A', 'B', or 'C' in the example code.