The state of string interpolation

H. S. Teoh hsteoh at quickfur.ath.cx
Fri Dec 7 01:47:13 UTC 2018


On Fri, Dec 07, 2018 at 12:43:43AM +0000, Mike Franklin via Digitalmars-d wrote:
[...]
> As for myself, I don't see much value in forcing users to add `mixin`
> at the site of a mixin template instantiation, but there's a lot I
> don't see, so I may just be missing something.
> ```
> import std.stdio;
> 
> mixin template Foo(T)
> {
>     T x = 5;
> }
> 
> extern(C) void main()
> {
>     // Why do we need `mixin` here?
>     // Why can't we just write `Foo!int;`?
>     // It's already yelling at us with a ! operator.
>     mixin Foo!int;
>     writeln(x);
> }
> ```

Yes it's already yelling at us with a ! operator, but the difference is
that Foo!Bar is generally read as a templated type of some sort in
typical D code.  It's surprising behaviour to see Foo!Bar that not only
has side-effects, but may access symbols in the current scope that
aren't explicitly passed to it.  The 'mixin' is the cue that whatever's
inside Foo may reach out and touch stuff in the surrounding code with
its grubby hands, as opposed to, say, instantiating a template type, or
calling a template function, both of which can only exert an effect
within their own scope and/or arguments explicitly handed to them.

While I understand the cumbersomeness of having to type `mixin`
everywhere string interpolation is used, part of me kinda wants to keep
the `mixin` in the syntax, if for nothing else than to warn anyone
reading the code that we're potentially touching stuff outside of what
template functions can usually touch.

Which leads to the absolute minimal language change that would let us
implement nice interpolated strings: mixin expressions. Writing:

	string name = ...;
	writeln(mixin interp!"My name is ${name}.");

is miles ahead of:

	string name = ...;
	mixin interp!"My name is ${name}.";
	writeln(interpResult);	// bad: `interpResult` is an implicit
				// symbol leaked out of interp's
				// implementation

Esp. if multiple interpolated arguments are being used:

	string name = ...;
	int age = ...;
	mixin interp!"My name is ${name}.";
	string s1 = interpResult;	// ouch
	mixin interp!"My age is ${age}.";
	string s2 = interpResult;	// double ouch
	writefln("%s %s", s1, s2);

Far too much boilerplate compared to:

	string name = ...;
	int age = ...;
	writefln("%s %s",
		 mixin interp!"My name is ${name}.",
		 mixin interp!"My age is ${age}.");

(I know, I know, bad example, since the whole point is not to use format
strings.  But it was the quickest example that came to mind.)

And yes, `mixin interp!"..."` is horribly verbose. But ostensibly we
could abbreviate it to something like `mixin i!"..."`.


On that note, though, a thought occurred to me: what if we implement
interpolated strings using something (admittedly a major hack) like:

	mixin(interpolate!q{
		writeln("My name is ${name} and age is ${age}");
		writeln("How are you, ${guestName}?");
	});

where `interpolate` parses the passed-in code, expands any interpolated
variables, and returns a code string that's mixed into the current
context?  It will allow completely convenient syntax, since you can put
multiple statements in the token string, even arbitrarily complex bits
of code if you want. All you have to do is to wrap the whole thing
inside the token string.

(I can already see y'all cringe at this. :-P  But for completeness' sake
in the prospective DIP, we should address why such an approach is not
desirable, since it's clearly *possible*, and does, ostensibly, solve
the problem at hand.)


T

-- 
Let's eat some disquits while we format the biskettes.


More information about the Digitalmars-d mailing list