"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