"string interpolation"

Adam D. Ruppe destructionator at gmail.com
Sun Jun 9 01:54:10 UTC 2019


On Sunday, 9 June 2019 at 00:53:23 UTC, Nicholas Wilson wrote:
> Specifically for the case for mixins they accept comma 
> separated stuff.

all that does is change ~ for , ...

> Stuff that is not a string will be converted through it 
> `.stringof` property (e.g. types).

And this is bad! .stringof is the wrong thing to do in the vast 
majority of mixin cases, especially now that we have static 
foreach.

> mixin(T, " ", id, " = ", value, ");");

This will actually generate wrong code in there coincidentally 
happens to be another thing in scope with the same name as T, and 
if not, it is liable to cause a compile error.

The correct way to write these things is

mixin("T " ~ id ~ " = value;");


In well-written mixin code, concatenation is infrequent 
(operators (think opBinary) and declaration names are the only 
ones I wouldn't question. there are a few exceptions, but you 
probably aren't one of them) and stringof is extremely rare. 
Thorughout all my code generation libraries, I have *zero* uses 
of .stringof inside mixin. In my code generation tests and 
samples, I have one use of it... and it predates `static 
foreach`. Let me show you this code.

The idea here was to do a pass-by-value lambda. Usage:

     auto foo(int x) @nogc
     {
         auto f = lambda!(x, q{ (int y) => x + y });
         return f;
     }

Note the @nogc part, which is why we aren't just using the 
built-in one and a string instead. You list the arguments you 
capture, then the lambda as a string.


Old version of the code:

---
     template lambda(Args...) {
     	import std.conv;
	import std.range;
	import std.string;

     	string evil() {
		// build the functor
		import std.meta;
		string code = "static struct anon {";
		foreach(i; aliasSeqOf!(iota(0, Args.length-1)))
			code ~= "typeof(Args[" ~ to!string(i) ~ "]) " ~ 
Args[i].stringof ~ ";";

		string func = Args[$-1];
		auto idx = func.indexOf("=>");
		if(idx == -1)
			throw new Exception("No => in lambda"); // or we could use one 
of the other styles

		auto args = func[0 .. idx];
		auto bod  = func[idx + 2 .. $];

		code ~= "auto opCall(T...)" ~ args ~ "{ return " ~ bod ~ "; }";

		code ~= "this(T...)(T t) {
			this.tupleof = t;
		};";

		code ~= "}";
		return code;
	}
     	mixin(evil());

         anon lambda() {
                 anon a;
                 // copy the values in
                 a.tupleof = Args[0 .. $-1];
                 return a;
         }
     }
---


It worked, but you can tell by my name of evil(), I didn't even 
like this code back when I wrote it the first time.

But I use a lot of concats and some stringof there. Let's try to 
rewrite this to eliminate that stuff with D's new features.

---
     template lambda(Args...) {

	static struct anon {
		static foreach(i; 0 .. Args.length - 1)
			mixin("typeof(Args[i]) " ~ __traits(identifier, Args[i]) ~ 
";");

		mixin("auto opCall(T...)"~Args[$-1][0 .. 
Args[$-1].indexOf("=>")]~" {
			return " ~ Args[$-1][Args[$-1].indexOf("=>") + 2 .. $] ~ ";
		}");
	}

	anon lambda() {
		anon a;
		// copy the values in
		a.tupleof = Args[0 .. $-1];
		return a;
	}
     }
---

No more stringof! No more conversion of values to string, meaning 
no more need for two of those imports! Yay. And compile time cut 
down by 1/4.

But, that is still a lot of concatenation, and it isn't a 
declaration name or an operator, so this code is still suspect to 
me. Can we do even better?

Of course! Let's replace that mixin generated opCall with 
something simpler:

        auto opCall(T...)(T t) {
            return mixin(Args[$-1])(t);
        }

Heeey :) it still works, and it is a LOT simpler. Zero imports 
needed now!

And you get better error messages and more compatibility with 
other D syntaxes. Let's take the type out.

With old code:

lll.d-mixin-53(53): Error: undefined identifier y
lll.d(15): Error: template lll.foo.lambda!(x, " (y) => x + y 
").anon.opCall cannot deduce function from argument types 
!()(int), candidates are:


But you defined y! So that error message doesn't help.

With new code:

lll.d(58): Error: function literal __lambda3(y) is not callable 
using argument types (int)
lll.d(58):        cannot pass argument _param_0 of type int to 
parameter y
lll.d(15): Error: template instance `lll.foo.lambda!(x, " (y) => 
x + y ").anon.opCall!int` error instantiating


Oh, the literal types aren't matching up. This message helps.


Let's change the lambda to (int y) { return x + y; }. You get a 
range error or a static assert error about the syntax, depending 
on how robust the implementation is.

But with the new code? No error message, it actually works. 
Instead of trying to slice up D code, I'm just compiling it as a 
complete chunk. And it still satisfies the @nogc constraint.


Mixins without stringof and minimizing concatenation is:

1) possible!

2) easier to write

3) easier to read

4) has fewer bugs


Instead of changing concat/stringof syntax, we should be 
educating people on the techniques to get rid of it. It is rarely 
justified.


More information about the Digitalmars-d mailing list